l="">
273
+* Scope: architecture review, use-case clarification, and operational guidance
274
+* Purpose: help internal teams better understand the system and plan future improvements
275
+
276
+This consultation is advisory in nature and does not include additional implementation or configuration work.
277
+
278
+---
279
+
280
+---
281
+
282
+### Optional Ongoing Support
283
+
284
+| Service                                | Cost (THB / month) |
285
+| -------------------------------------- | ------------------ |
286
+| IOC feed maintenance & updates         | 20,000 – 40,000    |
287
+| Rule tuning & false‑positive reduction | Included           |
288
+
289
+---
290
+
291
+## 8. Assumptions, Exclusions & Out-of-Scope
292
+
293
+**Assumptions**
294
+
295
+* Log sources are accessible and stable
296
+* Required access is provided during implementation
297
+* Log formats do not change significantly during the project timeline
298
+
299
+**Exclusions**
300
+
301
+* 24/7 SOC monitoring
302
+* Incident response execution or forensic investigation
303
+* Advanced UEBA or machine-learning analytics
304
+
305
+**Out-of-Scope (Unless Quoted Separately)**
306
+
307
+The following items are not included in this proposal and will require a separate quotation if requested:
308
+
309
+* Major changes to log formats, vendors, or network architecture after project kickoff
310
+* Onboarding of additional log sources beyond firewall, DNS, IDS/IPS, and VPN
311
+* Development of custom dashboards beyond standard operational views
312
+* Unlimited rule changes or ongoing rule development beyond the initial tuning period
313
+* Integration with additional third-party systems not listed in this proposal
314
+* Emergency or after-hours support outside agreed working hours
315
+* Compliance certification, audit execution, or regulatory reporting
316
+
317
+---
318
+
319
+## 9. Value to Customer
320
+
321
+* Practical, actionable security detection
322
+* Continuously updated threat intelligence
323
+* Reduced alert noise
324
+* Clear investigation and audit trail
325
+* Scalable foundation for future MDR services
326
+
327
+---
328
+
329
+## 10. Closing
330
+
331
+This implementation provides enterprise-grade detection and response capability using open, well-architected components—without vendor lock-in or unnecessary complexity.
332
+
333
+---
334
+
335
+# Appendix A: Use Case List (Initial Implementation Scope)
336
+
337
+The following use cases will be implemented and tuned as part of the initial project scope. Final severity and thresholds will be confirmed during log review and tuning.
338
+
339
+## A1. DNS / Firewall (IOC)
340
+
341
+| Category | Source            | Use Case                                              | Target Severity |
342
+| -------- | ----------------- | ----------------------------------------------------- | --------------- |
343
+| DNS      | Firewall/DNS logs | DNS Network Traffic – Communicate to Malicious Domain | Medium          |
344
+| DNS      | Firewall/DNS logs | DNS Network Traffic – Malicious Domain IOCs Detection | Medium          |
345
+
346
+## A2. FortiGate IPS/IDS & Firewall
347
+
348
+| Category | Source    | Use Case                                                 | Target Severity |
349
+| -------- | --------- | -------------------------------------------------------- | --------------- |
350
+| IPS      | FortiGate | IPS&IDS Network Traffic – Allowed RDP from Public IPs    | High            |
351
+| IPS      | FortiGate | IPS&IDS Firewall Account – Admin Password Change         | High            |
352
+| IPS      | FortiGate | IPS&IDS Firewall Account – Create/Add Admin Account      | High            |
353
+| IPS      | FortiGate | IPS&IDS Firewall Configure – Disabled Email Notification | High            |
354
+| IPS      | FortiGate | IPS&IDS Firewall Configure – Download Configure FW       | Low             |
355
+| IPS      | FortiGate | IPS&IDS IDS Alert – Multiple Critical/High               | Medium          |
356
+| IPS      | FortiGate | IPS&IDS Network Traffic – Port Scanning                  | Low             |
357
+| IPS      | FortiGate | IPS&IDS Network Traffic – IOC Detection                  | Medium          |
358
+| IPS      | FortiGate | IPS&IDS Network Traffic – Port Scanning from Private IP  | Medium          |
359
+| IPS      | FortiGate | IPS&IDS Network Traffic – Communicate to Malicious IP    | Medium          |
360
+
361
+## A3. FortiGate VPN
362
+
363
+| Category | Source    | Use Case                                                         | Target Severity |
364
+| -------- | --------- | ---------------------------------------------------------------- | --------------- |
365
+| VPN      | FortiGate | VPN – Authentication Success from Guest Account                  | High            |
366
+| VPN      | FortiGate | VPN – Authentication Success from Multiple Country               | High            |
367
+| VPN      | FortiGate | VPN – Authentication Brute Force Success                         | High            |
368
+| VPN      | FortiGate | VPN – Authentication Multiple Fail (Many Accounts from 1 Source) | Low             |
369
+| VPN      | FortiGate | VPN – Authentication Success from Outside Thailand               | High            |
370
+
371
+## A4. Windows / Active Directory
372
+
373
+| Category | Source                   | Use Case                                                              | Target Severity |
374
+| -------- | ------------------------ | --------------------------------------------------------------------- | --------------- |
375
+| Windows  | Windows Security Logs    | Windows Authentication – Multiple Fail from Privileged Account        | Medium          |
376
+| Windows  | Windows Security Logs    | Windows Authentication – Multiple Fail from Service Account           | Medium          |
377
+| Windows  | Windows AD Logs          | Windows AD – Enumeration with Malicious Tools                         | Medium          |
378
+| Windows  | Windows Security Logs    | Windows Authentication – Fail from Public IPs                         | Medium          |
379
+| Windows  | Windows Security Logs    | Windows File Share – Enumeration to Single Destination                | Medium          |
380
+| Windows  | Windows Security Logs    | Windows Authentication – Success from Public IPs                      | High            |
381
+| Windows  | Windows Security Logs    | Windows Authentication – Privileged Account Impersonation             | High            |
382
+| Windows  | Windows Security Logs    | Windows Authentication – Successful Pass the Hash RDP                 | High            |
383
+| Windows  | Windows Security Logs    | Windows Authentication – Success from Guest Account                   | High            |
384
+| Windows  | Windows Security Logs    | Windows Authentication – Interactive Logon Success by Service Account | High            |
385
+| Windows  | Windows Security Logs    | Windows Account – Added to Privileged Custom Group                    | High            |
386
+| Windows  | Windows Security Logs    | Windows Account – Added to Privileged Group                           | High            |
387
+| Windows  | Windows Domain Configure | Windows Domain Configure – DSRM Password Reset                        | High            |
388
+| Windows  | Windows Security Logs    | Windows Authentication – Multiple Fail (1 Account from Many Sources)  | Low             |
389
+| Windows  | Windows Security Logs    | Windows Authentication – Multiple Fail (Many Accounts from 1 Source)  | Low             |
390
+| Windows  | Windows Security Logs    | Windows Authentication – Multiple Fail from Guest Account             | Low             |
391
+| Windows  | Windows Security Logs    | Windows Authentication – Multiple Fail (1 Account from 1 Source)      | Low             |
392
+| Windows  | Windows Security Logs    | Windows Authentication – Multiple Interactive Logon Denied            | Low             |
393
+| Windows  | Windows Security Logs    | Windows Authentication – Password Spray                               | Low             |
394
+| Windows  | Windows Security Logs    | Windows Authentication – Attempt from Disabled Account                | Low             |
395
+| Windows  | Windows Security Logs    | Windows Domain Account – Created                                      | Low             |
396
+| Windows  | Windows Security Logs    | Windows Local Account – Re-Enabled                                    | Low             |
397
+| Windows  | Windows Security Logs    | Windows Local Account – Created                                       | Low             |
398
+| Windows  | Windows Security Logs    | Windows Domain Account – Re-Enabled                                   | Low             |
399
+
400
+---
401
+
402
+**Notes**
403
+
404
+* IOC-based detections require an IOC feed and update schedule. IOC matching and enrichment will be implemented via the automation layer.
405
+* Geo-based VPN detections require GeoIP enrichment and an exception list for approved overseas users.

Plik diff jest za duży
+ 48 - 0
Shuffle/.github/CONTRIBUTING.md


+ 12 - 0
Shuffle/.github/FUNDING.yml

1
+# These are supported funding model platforms
2
+
3
+github: frikky
4
+patreon: # Replace with a single Patreon username
5
+open_collective: shuffle
6
+ko_fi: frikky
7
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9
+liberapay: # Replace with a single Liberapay username
10
+issuehunt: # Replace with a single IssueHunt username
11
+otechie: # Replace with a single Otechie username
12
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

+ 31 - 0
Shuffle/.github/ISSUE_TEMPLATE/bug_report.md

1
+---
2
+name: Bug report
3
+about: Report a bug to help us improve
4
+title: ''
5
+labels: ''
6
+assignees: ''
7
+
8
+---
9
+
10
+**Describe the bug**
11
+A clear and concise description of what the bug is. If you can't execute a workflow, or an app isn't being created, an app isn't being created etc, please add debug logs (see further down)
12
+
13
+**To Reproduce**
14
+Steps to reproduce the behavior:
15
+1. Run installation '...'
16
+2. Create workflow '...'
17
+3. Click on '....'
18
+...
19
+
20
+**Expected behavior**
21
+A description of what you expected to happen.
22
+
23
+**Screenshots**
24
+If applicable, add screenshots to help explain your problem.
25
+
26
+** Debug logs (NOT APPLICABLE FOR CLOUD)**
27
+Run the following commands and paste them
28
+```
29
+docker logs shuffle-backend
30
+docker ps -a
31
+```

+ 22 - 0
Shuffle/.github/ISSUE_TEMPLATE/feature_request.md

1
+---
2
+name: Feature request
3
+about: Suggest an idea for this project
4
+title: ''
5
+labels: ''
6
+assignees: ''
7
+
8
+---
9
+
10
+**Is your feature request related to a problem? Please describe.**
11
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12
+
13
+**Describe the solution you'd like**
14
+A clear and concise description of what you want to happen.
15
+
16
+**Describe alternatives you've considered**
17
+A clear and concise description of any alternative solutions or features you've considered.
18
+
19
+**Additional context**
20
+Add any other context or screenshots about the feature request here.
21
+
22
+**Screenshots of where and how**

+ 11 - 0
Shuffle/.github/dependabot.yml

1
+# To get started with Dependabot version updates, you'll need to specify which
2
+# package ecosystems to update and where the package manifests are located.
3
+# Please see the documentation for all configuration options:
4
+# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5
+
6
+version: 2
7
+updates:
8
+  - package-ecosystem: "" # See documentation for possible values
9
+    directory: "/" # Location of package manifests
10
+    schedule:
11
+      interval: "weekly"

+ 38 - 0
Shuffle/.github/install-aws.md

1
+## Install Shuffle on AWS
2
+
3
+First, you need to create your own VPC for the same range of private IP addresses.
4
+
5
+**To create a VPC in AWS, follow these steps**
6
+
7
+1. Sign in to the AWS Management Console and open the Amazon VPC console at https://console.aws.amazon.com/vpc/.
8
+2. In the top navigation bar, choose the region in which you want to create the VPC.
9
+3. In the navigation pane, choose Your VPCs & Choose Create VPC.
10
+4. Enter a name for your VPC in the Name tag field & Choose VPC and more option. 
11
+5. Specify the IPv4 CIDR block for your VPC. The CIDR block is the range of IP addresses that will be available for use within your VPC. You can specify any CIDR block that is:
12
+  - Between a /16 and /28 netmask (inclusive)
13
+  - Not currently in use
14
+6. Specify the AZs, Number of public subnets & Number of private subnets.
15
+
16
+![image](https://user-images.githubusercontent.com/118437260/211500830-30c52dc0-0688-47f9-8ee7-eb7f9b31a9b8.png)
17
+
18
+7. Choose Yes, Create VPC.
19
+
20
+Your VPC will be created and will appear in the list of Your VPCs. By default, a VPC includes a default security group and a default network ACL. You can customize your VPC by adding subnets, security groups, network ACLs, and other resources.
21
+
22
+
23
+
24
+**To create an EC2 instance in AWS, follow these steps:**
25
+
26
+1. Sign in to the AWS Management Console and open the Amazon EC2 console at https://console.aws.amazon.com/ec2/.
27
+2. In the top navigation bar, choose the region in which you want to create the instance.
28
+3. In the navigation pane, choose Instances and Choose Launch Instance.
29
+4. On the Choose an Amazon Machine Image (AMI) page, choose an AMI. An AMI is a template that contains the software configuration (operating system, application     server, and applications) for your instance.
30
+5. On the Choose an Instance Type page, choose the hardware configuration of your instance. 
31
+6. On the Select an existing key pair or create a new key pair dialog box, choose an existing key pair or create a new one.
32
+7. On the network settings page and click on edit and select your VPC & subnet.
33
+8. On the Configure Security Group page, configure the security group for your instance. A security group acts as a virtual firewall for your instance to control inbound and outbound traffic.
34
+
35
+![image](https://user-images.githubusercontent.com/118437260/211514598-1c95e459-b98a-4579-b3a7-a92bf36e9f50.png)
36
+
37
+9. On the Add Storage page, add storage to your instance.
38
+10. Review your instance launch details and choose Launch.

Plik diff jest za duży
+ 126 - 0
Shuffle/.github/install-guide.html


+ 165 - 0
Shuffle/.github/install-guide.md

1
+<h1 align="center">
2
+
3
+[![Shuffle Logo](https://github.com/Shuffle/Shuffle/blob/main/frontend/public/images/Shuffle_logo_new.png)](https://shuffler.io)
4
+
5
+Shuffle Installation
6
+
7
+<div> 
8
+  <a
9
+      href="https://console.cloud.google.com/marketplace/product/shuffle-public/shuffle"
10
+      target="_blank"
11
+      style="text-decoration: none; display: flex; align-items: center; gap: 8px;"
12
+    >
13
+      <img
14
+        src="https://upload.wikimedia.org/wikipedia/commons/5/51/Google_Cloud_logo.svg"
15
+        height="20"
16
+        alt="Google Cloud Platform"
17
+      />
18
+    </a>
19
+</div>
20
+
21
+</h1>
22
+
23
+Installation of Shuffle is currently available for [docker](https://shuffler.io/docs/configuration#production-readiness) and [kubernetes](https://shuffler.io/docs/configuration#Kubernetes). 
24
+
25
+- Looking to run workflows onprem, but don't want to run the entire stack? Looks into [Environments & Orborus hosting Onprem](https://shuffler.io/docs/organizations#Environments)
26
+- Looking for how to update Shuffle? Check the [updating guide](https://shuffler.io/docs/configuration#updating_shuffle)
27
+
28
+This document outlines an introduction environment which is **not** scalable. [Read here](https://shuffler.io/docs/configuration#production_readiness) for information on production readiness and scalability. This also includes system requirements and configurations for **Docker Swarm** or **Kubernetes**. 
29
+
30
+
31
+# Docker - *nix
32
+The Docker setup is the default setup, and is ran with docker compose. This is [NOT a scalable build](https://shuffler.io/docs/configuration#production-readiness) without changes.
33
+
34
+**PS: if you're setting up Shuffle on Windows, go to the next step (Windows Docker setup)**
35
+
36
+1. Make sure you have [Docker](https://docs.docker.com/get-docker/) and [git](https://git-scm.com/downloads)(for downloading) installed, and that you have a minimum of **4Gb of RAM** available. More RAM = better.
37
+2. Download Shuffle
38
+```bash
39
+git clone https://github.com/Shuffle/Shuffle
40
+cd Shuffle
41
+```
42
+
43
+3. Fix prerequisites for the Opensearch database (Elasticsearch also works). This requires the `shuffle-database` folder to exist.
44
+```bash
45
+sudo chown -R 1000:1000 shuffle-database  # IF you get an error using 'chown', add the user first with 'sudo useradd opensearch'
46
+
47
+sudo swapoff -a                           # Disable swap
48
+```
49
+
50
+4. Run docker-compose.
51
+```bash
52
+docker compose up -d
53
+```
54
+
55
+5. Recommended for Opensearch to work well
56
+```bash
57
+sudo sysctl -w vm.max_map_count=262144             # https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html
58
+```
59
+
60
+When you're done, go to the [After installation](#after-installation) step below.
61
+
62
+## Windows with WSL  
63
+This step is for setting up with Docker on windows from scratch.
64
+
65
+1. Make sure you have [Docker](https://docs.docker.com/docker-for-windows/install/) and [docker-compose](https://docs.docker.com/compose/install/) installed. WSL2 may be required.
66
+
67
+2. Go to https://github.com/frikky/shuffle/releases and download the latest .zip release (or install git)
68
+
69
+3. Unzip the folder and enter it
70
+
71
+4. Open the .env file and change the line with "OUTER_HOSTNAME" to contain your IP:
72
+
73
+```bash
74
+OUTER_HOSTNAME=YOUR.IP.HERE
75
+```
76
+
77
+5. Run docker compose
78
+```bash
79
+docker compose up -d
80
+```
81
+
82
+### Configurations (high availability, scale, proxies, default users etc.)
83
+https://shuffler.io/docs/configuration
84
+
85
+![architecture](https://github.com/frikky/Shuffle/raw/main/frontend/src/assets/img/shuffle_architecture.png)
86
+
87
+### After installation 
88
+1. After installation, go to http://localhost:3001 (or your servername - https is on port 3443)
89
+2. Now set up your admin account (username & password). Shuffle doesn't have a default username and password. 
90
+3. Sign in with the same Username & Password! Go to /apps and see if you have any apps yet. If not - you may need to [configure proxies](https://shuffler.io/docs/configuration#production_readiness)
91
+4. Check out https://shuffler.io/docs/configuration as it has a lot of useful information to get started
92
+
93
+![Admin account setup](https://github.com/Shuffle/Shuffle/blob/main/frontend/src/assets/img/shuffle_adminaccount.png?raw=true)
94
+
95
+### Useful info
96
+* Check out [getting started](https://shuffler.io/docs/getting_started)
97
+* The default state of Shuffle is NOT scalable. See [production setup](https://shuffler.io/docs/configuration#production_readiness) for more info
98
+* The server is available on http://localhost:3001 (or your servername)
99
+* Further configurations can be done in docker-compose.yml and .env.
100
+* Default database location is in the same folder: ./shuffle-database
101
+
102
+# Local development installation
103
+
104
+Local development is pretty straight forward with **ReactJS** and **Golang**. This part is intended to help you run the code for development purposes. We recommend having Shuffle running with the Docker-compose, then manually running the portion that you want to test and/or edit.
105
+
106
+**PS: You have to stop the Backend Docker container to get this one working**
107
+
108
+**PPS: Use the "main" branch when developing to get it set up easier**
109
+
110
+## Frontend - ReactJS /w cytoscape
111
+http://localhost:3000 - Requires [npm](https://nodejs.org/en/download/)/[yarn](https://yarnpkg.com/lang/en/docs/install/#debian-stable)/your preferred manager. Runs independently from backend.
112
+```bash
113
+cd frontend
114
+yarn install
115
+yarn start
116
+```
117
+
118
+## Backend - Golang
119
+http://localhost:5001 - REST API - requires [>=go1.13](https://golang.org/dl/)
120
+```bash
121
+export SHUFFLE_OPENSEARCH_URL="https://localhost:9200"
122
+export SHUFFLE_ELASTIC=true
123
+export SHUFFLE_OPENSEARCH_USERNAME=admin
124
+export SHUFFLE_OPENSEARCH_PASSWORD=StrongShufflePassword321!
125
+export SHUFFLE_OPENSEARCH_SKIPSSL_VERIFY=true
126
+cd backend/go-app
127
+go run main.go walkoff.go docker.go
128
+```
129
+**WINDOWS USERS:** Follow [this guide](https://www.wikihow.com/Create-an-Environment-Variable-in-Windows-10) to add environment variables in your machine.
130
+
131
+Large portions of the backend is written in another repository - [shuffle-shared](https://github.com/frikky/shuffle-shared). If you want to update any of this code and test in realtime, we recommend following these steps:
132
+1. Clone shuffle-shared to a local repository
133
+2. Open the Shuffle backend's go.mod file (./shuffle/backend/go.mod)  (**NOT** in shuffle-shared)
134
+3. Change the following line to point to your directory AFTER the =>
135
+```
136
+//replace github.com/frikky/shuffle-shared => ../../shuffle-shared
137
+```
138
+4. Make the changes you want, then restart the backend server!
139
+5. With your changes made, make a pull request :fire:
140
+
141
+## Database - Opensearch 
142
+Make sure this is running through the docker-compose, and that the backend points to it with SHUFFLE_OPENSEARCH_URL defined.
143
+
144
+What it means:
145
+1. Make sure you have docker compose installed
146
+2. Make sure you have the docker-compose.yml file from this repository
147
+3. Run `docker-compose up opensearch -d`
148
+
149
+## Orborus
150
+Execution of Workflows:
151
+PS: This requires some specific environment variables
152
+```
153
+cd functions/onprem/orborus
154
+go run orborus.go
155
+```
156
+
157
+Environments (modify for Windows):
158
+```
159
+export ORG_ID=Shuffle
160
+export ENVIRONMENT_NAME=Shuffle
161
+export BASE_URL=http://YOUR-IP:5001
162
+export DOCKER_API_VERSION=1.40
163
+```
164
+
165
+AND THAT's it - hopefully it worked. If it didn't please email [frikky@shuffler.io](mailto:frikky@shuffler.io)

+ 86 - 0
Shuffle/.github/push_nightly.sh

1
+# This can be done in the dockerpush workflow itself
2
+# Done manually for now since GHCR isn't being pushed to easily with the current Github action CI. Nightly = Latest IF we run hotfixes on latest
3
+
4
+### Pull latest from ghcr CI/CD
5
+docker pull ghcr.io/shuffle/shuffle-app_sdk:nightly
6
+docker pull ghcr.io/shuffle/shuffle-worker:nightly
7
+docker pull ghcr.io/shuffle/shuffle-orborus:nightly
8
+docker pull ghcr.io/shuffle/shuffle-frontend:nightly
9
+#docker pull ghcr.io/shuffle/shuffle-backend:nightly
10
+#
11
+### NIGHTLY releases
12
+docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/frikky/shuffle-app_sdk:nightly
13
+docker tag ghcr.io/shuffle/shuffle-worker:nightly  ghcr.io/frikky/shuffle-worker:nightly
14
+docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/frikky/shuffle-orborus:nightly
15
+docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/frikky/shuffle-frontend:nightly
16
+docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/frikky/shuffle-backend:nightly
17
+
18
+docker push ghcr.io/frikky/shuffle-app_sdk:nightly
19
+docker push ghcr.io/frikky/shuffle-worker:nightly
20
+docker push ghcr.io/frikky/shuffle-orborus:nightly
21
+docker push ghcr.io/frikky/shuffle-frontend:nightly
22
+docker push ghcr.io/frikky/shuffle-backend:nightly
23
+
24
+### LATEST releases:
25
+## shuffle/shuffle 
26
+docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/shuffle/shuffle-app_sdk:latest
27
+docker tag ghcr.io/shuffle/shuffle-worker:nightly  ghcr.io/shuffle/shuffle-worker:latest
28
+docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/shuffle/shuffle-orborus:latest
29
+docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/shuffle/shuffle-frontend:latest
30
+docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/shuffle/shuffle-backend:latest
31
+
32
+docker push ghcr.io/shuffle/shuffle-app_sdk:latest
33
+docker push ghcr.io/shuffle/shuffle-worker:latest
34
+docker push ghcr.io/shuffle/shuffle-orborus:latest
35
+docker push ghcr.io/shuffle/shuffle-frontend:latest
36
+docker push ghcr.io/shuffle/shuffle-backend:latest
37
+
38
+## frikky/shuffle
39
+docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/frikky/shuffle-app_sdk:latest
40
+docker tag ghcr.io/shuffle/shuffle-worker:nightly  ghcr.io/frikky/shuffle-worker:latest
41
+docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/frikky/shuffle-orborus:latest
42
+docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/frikky/shuffle-frontend:latest
43
+docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/frikky/shuffle-backend:latest
44
+
45
+docker push ghcr.io/frikky/shuffle-app_sdk:latest
46
+docker push ghcr.io/frikky/shuffle-worker:latest
47
+docker push ghcr.io/frikky/shuffle-orborus:latest
48
+docker push ghcr.io/frikky/shuffle-frontend:latest
49
+docker push ghcr.io/frikky/shuffle-backend:latest
50
+
51
+
52
+### 1.1.0 releases:
53
+## shuffle/shuffle
54
+docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly ghcr.io/shuffle/shuffle-app_sdk:1.1.0
55
+docker tag ghcr.io/shuffle/shuffle-worker:nightly  ghcr.io/shuffle/shuffle-worker:1.1.0
56
+docker tag ghcr.io/shuffle/shuffle-orborus:nightly ghcr.io/shuffle/shuffle-orborus:1.1.0
57
+docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/shuffle/shuffle-frontend:1.1.0
58
+docker tag ghcr.io/shuffle/shuffle-backend:nightly ghcr.io/shuffle/shuffle-backend:1.1.0
59
+
60
+docker push ghcr.io/shuffle/shuffle-app_sdk:1.1.0
61
+docker push ghcr.io/shuffle/shuffle-worker:1.1.0
62
+docker push ghcr.io/shuffle/shuffle-orborus:1.1.0
63
+docker push ghcr.io/shuffle/shuffle-frontend:1.1.0
64
+docker push ghcr.io/shuffle/shuffle-backend:1.1.0
65
+
66
+## frikky/shuffle
67
+docker tag ghcr.io/shuffle/shuffle-app_sdk:nightly 	ghcr.io/frikky/shuffle-app_sdk:1.1.0
68
+docker tag ghcr.io/shuffle/shuffle-worker:nightly  	ghcr.io/frikky/shuffle-worker:1.1.0
69
+docker tag ghcr.io/shuffle/shuffle-orborus:nightly 	ghcr.io/frikky/shuffle-orborus:1.1.0
70
+docker tag ghcr.io/shuffle/shuffle-frontend:nightly ghcr.io/frikky/shuffle-frontend:1.1.0
71
+docker tag ghcr.io/shuffle/shuffle-backend:nightly 	ghcr.io/frikky/shuffle-backend:1.1.0
72
+
73
+docker push ghcr.io/frikky/shuffle-app_sdk:1.1.0
74
+docker push ghcr.io/frikky/shuffle-worker:1.1.0
75
+docker push ghcr.io/frikky/shuffle-orborus:1.1.0
76
+docker push ghcr.io/frikky/shuffle-frontend:1.1.0
77
+docker push ghcr.io/frikky/shuffle-backend:1.1.0
78
+
79
+### Manage worker-scale upload (Requires auth)
80
+# This is supposed to be unavailable, and only be downloadable by customers 
81
+docker pull ghcr.io/shuffle/shuffle-worker-scale:latest
82
+docker save ghcr.io/shuffle/shuffle-worker-scale:latest -o shuffle-worker.zip
83
+echo "1. Upload shuffle-worker.zip to the shuffler.io public repo. If in Github Dev env, download the file, and upload manually."
84
+echo "2. Have customers download it with: $ wget URL"
85
+echo "3. Have customers use with with: docker load shuffle-worker.zip"
86
+

+ 71 - 0
Shuffle/.github/workflows/codeql-analysis.yml

1
+# For most projects, this workflow file will not need changing; you simply need
2
+# to commit it to your repository.
3
+#
4
+# You may wish to alter this file to override the set of languages analyzed,
5
+# or to provide custom queries or build logic.
6
+#
7
+# ******** NOTE ********
8
+# We have attempted to detect the languages in your repository. Please check
9
+# the `language` matrix defined below to confirm you have the correct set of
10
+# supported CodeQL languages.
11
+#
12
+name: "CodeQL"
13
+
14
+on:
15
+  push:
16
+    branches: 
17
+      - master
18
+      - launch 
19
+  pull_request:
20
+    # The branches below must be a subset of the branches above
21
+    branches: 
22
+      - master
23
+      - launch 
24
+  schedule:
25
+    - cron: '38 16 * * 4'
26
+
27
+jobs:
28
+  analyze:
29
+    name: Analyze
30
+    runs-on: ubuntu-latest
31
+
32
+    strategy:
33
+      fail-fast: false
34
+      matrix:
35
+        language: [ 'go', 'javascript', 'python' ]
36
+        # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
37
+        # Learn more:
38
+        # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
39
+
40
+    steps:
41
+    - name: Checkout repository
42
+      uses: actions/checkout@v3
43
+
44
+    # Initializes the CodeQL tools for scanning.
45
+    - name: Initialize CodeQL
46
+      uses: github/codeql-action/init@v2
47
+      with:
48
+        languages: ${{ matrix.language }}
49
+        # If you wish to specify custom queries, you can do so here or in a config file.
50
+        # By default, queries listed here will override any specified in a config file.
51
+        # Prefix the list here with "+" to use these queries and those in the config file.
52
+        # queries: ./path/to/local/query, your-org/your-repo/queries@main
53
+
54
+    # Autobuild attempts to build any compiled languages  (C/C++, C#, or Java).
55
+    # If this step fails, then you should remove it and run the build manually (see below)
56
+    - name: Autobuild
57
+      uses: github/codeql-action/autobuild@v2
58
+
59
+    # ℹ️ Command-line programs to run using the OS shell.
60
+    # 📚 https://git.io/JvXDl
61
+
62
+    # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
63
+    #    and modify them (or add more) to build your code if your project
64
+    #    uses a compiled language
65
+
66
+    #- run: |
67
+    #   make bootstrap
68
+    #   make release
69
+
70
+    - name: Perform CodeQL Analysis
71
+      uses: github/codeql-action/analyze@v2

+ 82 - 0
Shuffle/.github/workflows/dockerbuild-nightly.yaml

1
+name: nightly-dockerbuild
2
+
3
+on:
4
+  workflow_dispatch:
5
+  push:
6
+    branches:
7
+      - nightly
8
+    paths:
9
+      - "**"
10
+      - "!.github/**"
11
+      - "!**.md"
12
+      - "!docker-compose.yml"
13
+jobs:
14
+  main:
15
+    runs-on: ubuntu-latest
16
+    continue-on-error: ${{ matrix.experimental }}
17
+    strategy:
18
+      fail-fast: false
19
+      matrix:
20
+        include:
21
+          - app: frontend
22
+            path: frontend
23
+            version: nightly
24
+            experimental: true
25
+          - app: backend
26
+            path: backend
27
+            version: nightly
28
+            experimental: true
29
+          - app: orborus
30
+            path: functions/onprem/orborus
31
+            version: nightly
32
+            experimental: true
33
+          - app: worker
34
+            path: functions/onprem/worker
35
+            version: nightly
36
+            experimental: true
37
+    steps:
38
+      - name: Checkout
39
+        uses: actions/checkout@v3
40
+
41
+      - name: Set up Docker Buildx
42
+        uses: docker/setup-buildx-action@v3
43
+
44
+      - name: Set up QEMU
45
+        uses: docker/setup-qemu-action@v3
46
+        with:
47
+          platforms: "amd64,arm64,arm"
48
+
49
+      - name: Login to DockerHub
50
+        uses: docker/login-action@v3
51
+        with:
52
+          username: ${{ secrets.DOCKERHUB_USERNAME }}
53
+          password: ${{ secrets.DOCKERHUB_TOKEN }}
54
+
55
+      - name: Login to Ghcr
56
+        uses: docker/login-action@v3
57
+        with:
58
+          registry: ghcr.io
59
+          username: ${{ github.actor }}
60
+          password: ${{ secrets.GITHUB_TOKEN }}
61
+
62
+      - name: Ghcr Build and push
63
+        id: docker_build
64
+        uses: docker/build-push-action@v4
65
+        env:
66
+          BUILDX_NO_DEFAULT_LOAD: true
67
+        with:
68
+          logout: false
69
+          context: ${{ matrix.path }}/
70
+          file: ${{ matrix.path }}/Dockerfile
71
+          platforms: linux/amd64,linux/arm64
72
+          push: true
73
+          cache-from: type=local,src=/tmp/.buildx-cache
74
+          cache-to: type=local,dest=/tmp/.buildx-cache
75
+          tags: |
76
+            ghcr.io/shuffle/shuffle-${{ matrix.app }}:${{ matrix.version }}
77
+            ${{ secrets.DOCKERHUB_USERNAME }}/shuffle-${{ matrix.app }}:${{ matrix.version }}
78
+            frikky/shuffle-${{ matrix.app }}:${{ matrix.version }}
79
+            frikky/shuffle:${{ matrix.app }}
80
+
81
+      - name: Image digest
82
+        run: echo ${{ steps.docker_build.outputs.digest }}

+ 83 - 0
Shuffle/.github/workflows/dockerbuild.yaml

1
+name: dockerbuild
2
+
3
+on:
4
+  workflow_dispatch:
5
+  push:
6
+    branches:
7
+      - main
8
+    paths:
9
+      - "**"
10
+      - "!.github/**"
11
+      - "!**.md"
12
+      - "!docker-compose.yml"
13
+jobs:
14
+  main:
15
+    runs-on: ubuntu-latest
16
+    continue-on-error: ${{ matrix.experimental }}
17
+    strategy:
18
+      fail-fast: false
19
+      matrix:
20
+        include:
21
+          - app: frontend
22
+            path: frontend
23
+            version: 2.2.0
24
+            experimental: true
25
+          - app: backend
26
+            path: backend
27
+            version: 2.2.0
28
+            experimental: true
29
+          - app: orborus
30
+            path: functions/onprem/orborus
31
+            version: 2.2.0
32
+            experimental: true
33
+          - app: worker
34
+            path: functions/onprem/worker
35
+            version: 2.2.0
36
+            experimental: true
37
+    steps:
38
+      - name: Checkout
39
+        uses: actions/checkout@v3
40
+
41
+      - name: Set up Docker Buildx
42
+        uses: docker/setup-buildx-action@v3
43
+
44
+      - name: Set up QEMU
45
+        uses: docker/setup-qemu-action@v3
46
+        with:
47
+          platforms: "amd64,arm64,arm"
48
+
49
+      - name: Login to DockerHub
50
+        uses: docker/login-action@v3
51
+        with:
52
+          username: ${{ secrets.DOCKERHUB_USERNAME }}
53
+          password: ${{ secrets.DOCKERHUB_TOKEN }}
54
+
55
+      - name: Login to Ghcr
56
+        uses: docker/login-action@v3
57
+        with:
58
+          registry: ghcr.io
59
+          username: ${{ github.actor }}
60
+          password: ${{ secrets.GITHUB_TOKEN }}
61
+
62
+      - name: Ghcr Build and push
63
+        id: docker_build
64
+        uses: docker/build-push-action@v4
65
+        env:
66
+          BUILDX_NO_DEFAULT_LOAD: true
67
+        with:
68
+          logout: false
69
+          context: ${{ matrix.path }}/
70
+          file: ${{ matrix.path }}/Dockerfile
71
+          platforms: linux/amd64,linux/arm64
72
+          push: true
73
+          cache-from: type=local,src=/tmp/.buildx-cache
74
+          cache-to: type=local,dest=/tmp/.buildx-cache
75
+          tags: |
76
+            ghcr.io/shuffle/shuffle-${{ matrix.app }}:${{ matrix.version }}
77
+            ghcr.io/shuffle/shuffle-${{ matrix.app }}:latest
78
+            ${{ secrets.DOCKERHUB_USERNAME }}/shuffle-${{ matrix.app }}:${{ matrix.version }}
79
+            frikky/shuffle-${{ matrix.app }}:${{ matrix.version }}
80
+            frikky/shuffle:${{ matrix.app }}
81
+
82
+      - name: Image digest
83
+        run: echo ${{ steps.docker_build.outputs.digest }}

+ 77 - 0
Shuffle/.github/workflows/helm-release.yml

1
+name: Helm Release
2
+
3
+on:
4
+  release:
5
+    types: [published]
6
+    branches:
7
+      - main
8
+      - nightly
9
+  push:
10
+    branches:
11
+      - nightly
12
+      - main
13
+    paths:
14
+      - "functions/kubernetes/charts/**"
15
+  workflow_dispatch:
16
+
17
+permissions:
18
+  contents: read
19
+  packages: write
20
+
21
+jobs:
22
+  main:
23
+    runs-on: ubuntu-latest
24
+    steps:
25
+      - name: Checkout
26
+        uses: actions/checkout@v4
27
+
28
+      - name: Setup Helm
29
+        uses: azure/setup-helm@v4
30
+        with:
31
+          version: v3.18.4 # I run it locally so I know it works :b
32
+
33
+      - name: Verify Helm
34
+        run: helm version
35
+
36
+      - name: Set versions
37
+        run: |
38
+          if [[ ${{ github.event_name }} == 'release' ]]; then
39
+            TAG_NAME="${{ github.event.release.tag_name }}"
40
+
41
+            # Remove the v prefix
42
+            VERSION=${TAG_NAME#v}
43
+
44
+            APP_VERSION="${VERSION}"
45
+            CHART_VERSION="${VERSION}"
46
+          else
47
+            APP_VERSION="latest"
48
+            CHART_VERSION="2.2.0"
49
+          fi
50
+
51
+          echo "APP_VERSION set to ${APP_VERSION}"
52
+          echo "CHART_VERSION set to ${CHART_VERSION}. Validating..."
53
+
54
+          # https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
55
+          SEMVER_REGEX="^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
56
+
57
+          if echo "${CHART_VERSION}" | grep -Pq "${SEMVER_REGEX}"; then
58
+            echo "${CHART_VERSION} is a valid SemVer string";
59
+          else
60
+            echo "${CHART_VERSION} is an invalid SemVer string";
61
+            exit 1;
62
+          fi
63
+
64
+          echo "CHART_VERSION=${CHART_VERSION}" >> "$GITHUB_ENV"
65
+          echo "APP_VERSION=${APP_VERSION}" >> "$GITHUB_ENV"
66
+
67
+      - name: Update helm dependencies
68
+        run: helm dependency update ./functions/kubernetes/charts/shuffle
69
+
70
+      - name: Package Helm chart
71
+        run: helm package ./functions/kubernetes/charts/shuffle --version "${CHART_VERSION}" --app-version="${APP_VERSION}" --destination ./functions/kubernetes/charts
72
+
73
+      - name: Login to OCI registry (ghcr.io)
74
+        run: helm registry login ghcr.io --username ${{ github.actor }} --password ${{ secrets.GITHUB_TOKEN }}
75
+
76
+      - name: Push helm chart
77
+        run: helm push ./functions/kubernetes/charts/shuffle-*.tgz oci://ghcr.io/shuffle/charts

+ 31 - 0
Shuffle/.github/workflows/helm-test.yml

1
+name: Helm Test
2
+
3
+on:
4
+  push:
5
+    paths:
6
+      - "functions/kubernetes/charts/**"
7
+  workflow_dispatch:
8
+
9
+permissions:
10
+  contents: read
11
+
12
+jobs:
13
+  main:
14
+    runs-on: ubuntu-latest
15
+    steps:
16
+      - name: Checkout
17
+        uses: actions/checkout@v4
18
+
19
+      - name: Install apt dependencies
20
+        run: |
21
+          sudo apt-get install apt-transport-https -y --no-install-recommends
22
+          curl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey | gpg --dearmor | sudo tee /usr/share/keyrings/helm.gpg > /dev/null
23
+          echo "deb [signed-by=/usr/share/keyrings/helm.gpg] https://packages.buildkite.com/helm-linux/helm-debian/any/ any main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list
24
+          sudo apt-get update
25
+          sudo apt-get install helm -y --no-install-recommends
26
+
27
+      - name: Update helm dependencies
28
+        run: helm dependency update ./functions/kubernetes/charts/shuffle
29
+
30
+      - name: Template helm chart using default values
31
+        run: helm template shuffle ./functions/kubernetes/charts/shuffle --debug --values ./functions/kubernetes/charts/shuffle/values.yaml

+ 174 - 0
Shuffle/.github/workflows/quick-testing.yml

1
+name: Docker Compose Up and Ping
2
+
3
+on:
4
+  workflow_run:
5
+    workflows: ["dockerbuild"]
6
+    types:
7
+      - completed
8
+  workflow_dispatch:
9
+
10
+jobs:
11
+  build:
12
+    runs-on: ${{ matrix.os }}
13
+    strategy:
14
+      matrix:
15
+        os: [ubuntu-latest]
16
+        architecture: [x64, arm64]
17
+    steps:
18
+      - name: Checkout code
19
+        uses: actions/checkout@v2
20
+
21
+      - name: Shai-Hulud 2.0 Detector
22
+        uses: gensecaihq/Shai-Hulud-2.0-Detector@v1.0.0
23
+
24
+      - name: Install Docker Compose
25
+        run: |
26
+          sudo apt-get update
27
+          sudo apt-get install -y docker-compose
28
+          docker-compose --version
29
+
30
+      - name: Set up opensearch directory
31
+        run: chmod -R 777 shuffle-database
32
+
33
+      - name: Build the stack
34
+        run: docker compose up -d
35
+
36
+      - name: Wait for 30 seconds
37
+        run: sleep 30
38
+
39
+      - name: Check for restarting containers in a loop and fixing perms again
40
+        run: |
41
+          # echo "Changing permissions on shuffle-database directory again"
42
+          # chmod -R 777 shuffle-database
43
+
44
+          ATTEMPTS=30  # Total time = ATTEMPTS * 5 seconds = 30 seconds
45
+          for i in $(seq 1 $ATTEMPTS); do
46
+            RESTARTING_CONTAINERS=$(docker ps --filter "status=restarting" --format "{{.Names}}")
47
+            if [ -n "$RESTARTING_CONTAINERS" ]; then
48
+              echo "The following containers are restarting:"
49
+              echo "$RESTARTING_CONTAINERS"
50
+              exit 1
51
+            fi
52
+            echo "No containers are restarting. Attempt $i/$ATTEMPTS."
53
+            sleep 1
54
+          done
55
+          echo "No containers were found in a restarting state after $ATTEMPTS checks."
56
+
57
+      - name: Check if the response from the frontend contains the word "Shuffle"
58
+        run: |
59
+          RESPONSE=$(curl -s http://localhost:3001)
60
+          if echo "$RESPONSE" | grep -q "Shuffle"; then
61
+            echo "The word 'Shuffle' was found in the response."
62
+          else
63
+            echo "The word 'Shuffle' was not found in the response."
64
+            exit 1
65
+          fi
66
+      - name: Register a user and check the status code
67
+        run: |
68
+          MAX_RETRIES=30
69
+          RETRY_INTERVAL=10
70
+          CONTAINER_NAME="shuffle-backend"
71
+
72
+          for (( i=1; i<=$MAX_RETRIES; i++ ))
73
+          do
74
+            STATUS_CODE=$(curl -s -o /dev/null -w "%{http_code}" 'http://localhost:3001/api/v1/register' \
75
+              -H 'Accept: */*' \
76
+              -H 'Accept-Language: en-US,en;q=0.9' \
77
+              -H 'Connection: keep-alive' \
78
+              -H 'Content-Type: application/json' \
79
+              --data-raw '{"username":"demo@demo.io","password":"supercoolpassword"}')
80
+
81
+            if [ "$STATUS_CODE" -eq 200 ]; then
82
+              echo "User registration was successful with status code 200."
83
+              exit 0
84
+            elif [ "$STATUS_CODE" -ne 502 ]; then
85
+              echo "User registration failed with status code $STATUS_CODE."
86
+              exit 1
87
+            fi
88
+
89
+            echo "Received status code $STATUS_CODE. Retrying in $RETRY_INTERVAL seconds... ($i/$MAX_RETRIES)"
90
+            echo "Fetching last 30 lines of logs from container $CONTAINER_NAME..."
91
+
92
+            logs_output=$(docker logs --tail 30 "$CONTAINER_NAME")
93
+            echo "$logs_output"
94
+
95
+            echo "Fetching last 30 lines of logs from container shuffle-opensearch..."
96
+
97
+            opensearch_logs=$(docker logs --tail 30 shuffle-opensearch)
98
+            echo "$opensearch_logs"
99
+
100
+            sleep $RETRY_INTERVAL
101
+          done
102
+
103
+          echo "User registration failed after $MAX_RETRIES attempts."
104
+          exit 1
105
+
106
+      - name: Run Selenium testing for frontend
107
+        run: |
108
+          cd $GITHUB_WORKSPACE/frontend
109
+          # write some log to see the current directory
110
+          chmod +x frontend-testing.sh
111
+          ./frontend-testing.sh
112
+
113
+      - name: Get the API key and run a health check
114
+        id: health_check
115
+        run: |
116
+          RESPONSE=$(curl -s -k -u admin:StrongShufflePassword321! 'https://localhost:9200/users/_search')
117
+          echo "Raw Response: $RESPONSE"
118
+
119
+          API_KEY=$(echo "$RESPONSE" | jq -r '.hits.hits[0]._source.apikey')
120
+          if [ -n "$API_KEY" ] && [ "$API_KEY" != "null" ]; then
121
+            echo "Admin API key: $API_KEY"
122
+            echo "API_KEY=$API_KEY" >> $GITHUB_ENV
123
+          else
124
+            echo "Failed to retrieve the API key for the admin user."
125
+            exit 1
126
+          fi
127
+
128
+          echo "Waiting 1 minute before sending the health API request..."
129
+          sleep 60
130
+
131
+          echo "Checking health API..."
132
+          HEALTH_RESPONSE=$(curl -s 'http://localhost:3001/api/v1/health?force=true' \
133
+            -H "Authorization: Bearer $API_KEY")
134
+
135
+          echo "Health API Response: $HEALTH_RESPONSE"
136
+
137
+          WORKFLOWS_CREATE=$(echo "$HEALTH_RESPONSE" | jq -r '.workflows.create')
138
+          WORKFLOWS_RUN=$(echo "$HEALTH_RESPONSE" | jq -r '.workflows.run')
139
+          WORKFLOWS_RUN_FINISHED=$(echo "$HEALTH_RESPONSE" | jq -r '.workflows.run_finished')
140
+          WORKFLOWS_RUN_STATUS=$(echo "$HEALTH_RESPONSE" | jq -r '.workflows.run_status')
141
+          WORKFLOWS_DELETE=$(echo "$HEALTH_RESPONSE" | jq -r '.workflows.delete')
142
+
143
+          echo "WORKFLOWS_CREATE: $WORKFLOWS_CREATE"
144
+          echo "WORKFLOWS_RUN: $WORKFLOWS_RUN"
145
+          echo "WORKFLOWS_RUN_FINISHED: $WORKFLOWS_RUN_FINISHED"
146
+          echo "WORKFLOWS_RUN_STATUS: $WORKFLOWS_RUN_STATUS"
147
+          echo "WORKFLOWS_DELETE: $WORKFLOWS_DELETE"
148
+
149
+          if [ "$WORKFLOWS_CREATE" = "true" ] && [ "$WORKFLOWS_RUN" = "true" ] && [ "$WORKFLOWS_RUN_FINISHED" = "true" ] && [ "$WORKFLOWS_RUN_STATUS" = "FINISHED" ] && [ "$WORKFLOWS_DELETE" = "true" ]; then
150
+            echo "Health endpoint check was successful."
151
+            exit 0
152
+          else
153
+            echo "Health check failed."
154
+            exit 1
155
+          fi
156
+
157
+  notify:
158
+    needs: build
159
+    if: failure()
160
+    runs-on: ubuntu-latest
161
+    steps:
162
+      - name: Send Twilio Alert
163
+        run: |
164
+          WORKFLOW_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
165
+          MESSAGE="🚨 Shuffle Workflow Failed!
166
+          Repository: ${{ github.repository }}
167
+          Branch: ${{ github.ref_name }}
168
+          Workflow URL: $WORKFLOW_URL"
169
+
170
+          curl -s -X POST https://api.twilio.com/2010-04-01/Accounts/${{ secrets.TWILIO_ACCOUNT_SID }}/Messages.json \
171
+              --data-urlencode "To=${{ secrets.TWILIO_TO_NUMBER }}" \
172
+              --data-urlencode "From=${{ secrets.TWILIO_FROM_NUMBER }}" \
173
+              --data-urlencode "Body=$MESSAGE" \
174
+              -u "${{ secrets.TWILIO_ACCOUNT_SID }}:${{ secrets.TWILIO_AUTH_TOKEN }}" > /dev/null

+ 92 - 0
Shuffle/.github/workflows/tagged-nightly-release.yaml

1
+name: Tagged Nightly Release
2
+on:
3
+  release:
4
+    types: [published]
5
+    branches:
6
+      - nightly
7
+
8
+# there is a very clear point to this existing.
9
+# we want to also release versions that look like this: 
10
+# v2.1.0-nightly-date, v2.1.0-nightly-date-1, v2.1.0-nightly-date-2
11
+# we NEVER want to send customers a "nightly" tag. We always want to send them 
12
+# a tagged nightly tag. So that when something breaks, They can always
13
+# point to it.
14
+
15
+jobs:
16
+  main:
17
+    runs-on: ubuntu-latest
18
+    continue-on-error: ${{ matrix.experimental }}
19
+    strategy:
20
+      fail-fast: false
21
+      matrix:
22
+        include:
23
+          - app: frontend
24
+            path: frontend
25
+            experimental: true
26
+          - app: backend
27
+            path: backend
28
+            experimental: true
29
+          - app: app_sdk
30
+            path: backend/app_sdk
31
+            experimental: true
32
+          - app: orborus
33
+            path: functions/onprem/orborus
34
+            experimental: true
35
+          - app: worker
36
+            path: functions/onprem/worker
37
+            experimental: true
38
+    steps:
39
+      - name: Checkout
40
+        uses: actions/checkout@v3
41
+      
42
+      - name: Set version
43
+        id: set_version
44
+        run: |
45
+          if [[ ${{ github.event_name }} == 'release' ]]; then
46
+            echo "VERSION=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT
47
+          else
48
+            echo "VERSION=nightly-untagged-latest" >> $GITHUB_OUTPUT
49
+          fi
50
+
51
+      - name: Set up Docker Buildx
52
+        uses: docker/setup-buildx-action@v3
53
+
54
+      - name: Set up QEMU
55
+        uses: docker/setup-qemu-action@v3
56
+        with:
57
+          platforms: "amd64,arm64,arm"
58
+
59
+      - name: Login to DockerHub
60
+        uses: docker/login-action@v3
61
+        with:
62
+          username: ${{ secrets.DOCKERHUB_USERNAME }}
63
+          password: ${{ secrets.DOCKERHUB_TOKEN }}
64
+
65
+      - name: Login to Ghcr
66
+        uses: docker/login-action@v3
67
+        with:
68
+          registry: ghcr.io
69
+          username: ${{ github.actor }}
70
+          password: ${{ secrets.GITHUB_TOKEN }}
71
+
72
+      - name: Ghcr Build and push
73
+        id: docker_build
74
+        uses: docker/build-push-action@v4
75
+        env:
76
+          BUILDX_NO_DEFAULT_LOAD: true
77
+        with:
78
+          logout: false
79
+          context: ${{ matrix.path }}/
80
+          file: ${{ matrix.path }}/Dockerfile
81
+          platforms: linux/amd64,linux/arm64
82
+          push: true
83
+          cache-from: type=local,src=/tmp/.buildx-cache
84
+          cache-to: type=local,dest=/tmp/.buildx-cache
85
+          tags: |
86
+            ghcr.io/shuffle/shuffle-${{ matrix.app }}:${{ steps.set_version.outputs.VERSION }}
87
+            ${{ secrets.DOCKERHUB_USERNAME }}/shuffle-${{ matrix.app }}:${{ steps.set_version.outputs.VERSION }}
88
+            frikky/shuffle-${{ matrix.app }}:${{ steps.set_version.outputs.VERSION }}
89
+            frikky/shuffle:${{ matrix.app }}
90
+
91
+      - name: Image digest
92
+        run: echo ${{ steps.docker_build.outputs.digest }}

+ 32 - 0
Shuffle/.gitignore

1
+*node_modules/
2
+*build/
3
+*.lock
4
+*.swo
5
+*.swp
6
+*.swn
7
+*__pycache__*
8
+
9
+Shuffle-*.json
10
+backend/go-app/generated*
11
+
12
+functions/generated_apps
13
+*.zip
14
+
15
+*openapi-parsers/generated/*
16
+*openapi-parsers/other/*
17
+
18
+
19
+backend/onprem/app_sdk/apps
20
+*test.py
21
+
22
+*.exe
23
+*debug*
24
+!**/RuntimeDebugger.jsx
25
+
26
+shuffle-database/batch_metrics_enabled.conf
27
+shuffle-database/logging_enabled.conf
28
+shuffle-database/nodes
29
+shuffle-database/performance_analyzer_enabled.conf
30
+shuffle-database/rca_enabled.conf
31
+
32
+#*/package-lock.json

+ 661 - 0
Shuffle/LICENSE

1
+                    GNU AFFERO GENERAL PUBLIC LICENSE
2
+                       Version 3, 19 November 2007
3
+
4
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
+ Everyone is permitted to copy and distribute verbatim copies
6
+ of this license document, but changing it is not allowed.
7
+
8
+                            Preamble
9
+
10
+  The GNU Affero General Public License is a free, copyleft license for
11
+software and other kinds of works, specifically designed to ensure
12
+cooperation with the community in the case of network server software.
13
+
14
+  The licenses for most software and other practical works are designed
15
+to take away your freedom to share and change the works.  By contrast,
16
+our General Public Licenses are intended to guarantee your freedom to
17
+share and change all versions of a program--to make sure it remains free
18
+software for all its users.
19
+
20
+  When we speak of free software, we are referring to freedom, not
21
+price.  Our General Public Licenses are designed to make sure that you
22
+have the freedom to distribute copies of free software (and charge for
23
+them if you wish), that you receive source code or can get it if you
24
+want it, that you can change the software or use pieces of it in new
25
+free programs, and that you know you can do these things.
26
+
27
+  Developers that use our General Public Licenses protect your rights
28
+with two steps: (1) assert copyright on the software, and (2) offer
29
+you this License which gives you legal permission to copy, distribute
30
+and/or modify the software.
31
+
32
+  A secondary benefit of defending all users' freedom is that
33
+improvements made in alternate versions of the program, if they
34
+receive widespread use, become available for other developers to
35
+incorporate.  Many developers of free software are heartened and
36
+encouraged by the resulting cooperation.  However, in the case of
37
+software used on network servers, this result may fail to come about.
38
+The GNU General Public License permits making a modified version and
39
+letting the public access it on a server without ever releasing its
40
+source code to the public.
41
+
42
+  The GNU Affero General Public License is designed specifically to
43
+ensure that, in such cases, the modified source code becomes available
44
+to the community.  It requires the operator of a network server to
45
+provide the source code of the modified version running there to the
46
+users of that server.  Therefore, public use of a modified version, on
47
+a publicly accessible server, gives the public access to the source
48
+code of the modified version.
49
+
50
+  An older license, called the Affero General Public License and
51
+published by Affero, was designed to accomplish similar goals.  This is
52
+a different license, not a version of the Affero GPL, but Affero has
53
+released a new version of the Affero GPL which permits relicensing under
54
+this license.
55
+
56
+  The precise terms and conditions for copying, distribution and
57
+modification follow.
58
+
59
+                       TERMS AND CONDITIONS
60
+
61
+  0. Definitions.
62
+
63
+  "This License" refers to version 3 of the GNU Affero General Public License.
64
+
65
+  "Copyright" also means copyright-like laws that apply to other kinds of
66
+works, such as semiconductor masks.
67
+
68
+  "The Program" refers to any copyrightable work licensed under this
69
+License.  Each licensee is addressed as "you".  "Licensees" and
70
+"recipients" may be individuals or organizations.
71
+
72
+  To "modify" a work means to copy from or adapt all or part of the work
73
+in a fashion requiring copyright permission, other than the making of an
74
+exact copy.  The resulting work is called a "modified version" of the
75
+earlier work or a work "based on" the earlier work.
76
+
77
+  A "covered work" means either the unmodified Program or a work based
78
+on the Program.
79
+
80
+  To "propagate" a work means to do anything with it that, without
81
+permission, would make you directly or secondarily liable for
82
+infringement under applicable copyright law, except executing it on a
83
+computer or modifying a private copy.  Propagation includes copying,
84
+distribution (with or without modification), making available to the
85
+public, and in some countries other activities as well.
86
+
87
+  To "convey" a work means any kind of propagation that enables other
88
+parties to make or receive copies.  Mere interaction with a user through
89
+a computer network, with no transfer of a copy, is not conveying.
90
+
91
+  An interactive user interface displays "Appropriate Legal Notices"
92
+to the extent that it includes a convenient and prominently visible
93
+feature that (1) displays an appropriate copyright notice, and (2)
94
+tells the user that there is no warranty for the work (except to the
95
+extent that warranties are provided), that licensees may convey the
96
+work under this License, and how to view a copy of this License.  If
97
+the interface presents a list of user commands or options, such as a
98
+menu, a prominent item in the list meets this criterion.
99
+
100
+  1. Source Code.
101
+
102
+  The "source code" for a work means the preferred form of the work
103
+for making modifications to it.  "Object code" means any non-source
104
+form of a work.
105
+
106
+  A "Standard Interface" means an interface that either is an official
107
+standard defined by a recognized standards body, or, in the case of
108
+interfaces specified for a particular programming language, one that
109
+is widely used among developers working in that language.
110
+
111
+  The "System Libraries" of an executable work include anything, other
112
+than the work as a whole, that (a) is included in the normal form of
113
+packaging a Major Component, but which is not part of that Major
114
+Component, and (b) serves only to enable use of the work with that
115
+Major Component, or to implement a Standard Interface for which an
116
+implementation is available to the public in source code form.  A
117
+"Major Component", in this context, means a major essential component
118
+(kernel, window system, and so on) of the specific operating system
119
+(if any) on which the executable work runs, or a compiler used to
120
+produce the work, or an object code interpreter used to run it.
121
+
122
+  The "Corresponding Source" for a work in object code form means all
123
+the source code needed to generate, install, and (for an executable
124
+work) run the object code and to modify the work, including scripts to
125
+control those activities.  However, it does not include the work's
126
+System Libraries, or general-purpose tools or generally available free
127
+programs which are used unmodified in performing those activities but
128
+which are not part of the work.  For example, Corresponding Source
129
+includes interface definition files associated with source files for
130
+the work, and the source code for shared libraries and dynamically
131
+linked subprograms that the work is specifically designed to require,
132
+such as by intimate data communication or control flow between those
133
+subprograms and other parts of the work.
134
+
135
+  The Corresponding Source need not include anything that users
136
+can regenerate automatically from other parts of the Corresponding
137
+Source.
138
+
139
+  The Corresponding Source for a work in source code form is that
140
+same work.
141
+
142
+  2. Basic Permissions.
143
+
144
+  All rights granted under this License are granted for the term of
145
+copyright on the Program, and are irrevocable provided the stated
146
+conditions are met.  This License explicitly affirms your unlimited
147
+permission to run the unmodified Program.  The output from running a
148
+covered work is covered by this License only if the output, given its
149
+content, constitutes a covered work.  This License acknowledges your
150
+rights of fair use or other equivalent, as provided by copyright law.
151
+
152
+  You may make, run and propagate covered works that you do not
153
+convey, without conditions so long as your license otherwise remains
154
+in force.  You may convey covered works to others for the sole purpose
155
+of having them make modifications exclusively for you, or provide you
156
+with facilities for running those works, provided that you comply with
157
+the terms of this License in conveying all material for which you do
158
+not control copyright.  Those thus making or running the covered works
159
+for you must do so exclusively on your behalf, under your direction
160
+and control, on terms that prohibit them from making any copies of
161
+your copyrighted material outside their relationship with you.
162
+
163
+  Conveying under any other circumstances is permitted solely under
164
+the conditions stated below.  Sublicensing is not allowed; section 10
165
+makes it unnecessary.
166
+
167
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
168
+
169
+  No covered work shall be deemed part of an effective technological
170
+measure under any applicable law fulfilling obligations under article
171
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
172
+similar laws prohibiting or restricting circumvention of such
173
+measures.
174
+
175
+  When you convey a covered work, you waive any legal power to forbid
176
+circumvention of technological measures to the extent such circumvention
177
+is effected by exercising rights under this License with respect to
178
+the covered work, and you disclaim any intention to limit operation or
179
+modification of the work as a means of enforcing, against the work's
180
+users, your or third parties' legal rights to forbid circumvention of
181
+technological measures.
182
+
183
+  4. Conveying Verbatim Copies.
184
+
185
+  You may convey verbatim copies of the Program's source code as you
186
+receive it, in any medium, provided that you conspicuously and
187
+appropriately publish on each copy an appropriate copyright notice;
188
+keep intact all notices stating that this License and any
189
+non-permissive terms added in accord with section 7 apply to the code;
190
+keep intact all notices of the absence of any warranty; and give all
191
+recipients a copy of this License along with the Program.
192
+
193
+  You may charge any price or no price for each copy that you convey,
194
+and you may offer support or warranty protection for a fee.
195
+
196
+  5. Conveying Modified Source Versions.
197
+
198
+  You may convey a work based on the Program, or the modifications to
199
+produce it from the Program, in the form of source code under the
200
+terms of section 4, provided that you also meet all of these conditions:
201
+
202
+    a) The work must carry prominent notices stating that you modified
203
+    it, and giving a relevant date.
204
+
205
+    b) The work must carry prominent notices stating that it is
206
+    released under this License and any conditions added under section
207
+    7.  This requirement modifies the requirement in section 4 to
208
+    "keep intact all notices".
209
+
210
+    c) You must license the entire work, as a whole, under this
211
+    License to anyone who comes into possession of a copy.  This
212
+    License will therefore apply, along with any applicable section 7
213
+    additional terms, to the whole of the work, and all its parts,
214
+    regardless of how they are packaged.  This License gives no
215
+    permission to license the work in any other way, but it does not
216
+    invalidate such permission if you have separately received it.
217
+
218
+    d) If the work has interactive user interfaces, each must display
219
+    Appropriate Legal Notices; however, if the Program has interactive
220
+    interfaces that do not display Appropriate Legal Notices, your
221
+    work need not make them do so.
222
+
223
+  A compilation of a covered work with other separate and independent
224
+works, which are not by their nature extensions of the covered work,
225
+and which are not combined with it such as to form a larger program,
226
+in or on a volume of a storage or distribution medium, is called an
227
+"aggregate" if the compilation and its resulting copyright are not
228
+used to limit the access or legal rights of the compilation's users
229
+beyond what the individual works permit.  Inclusion of a covered work
230
+in an aggregate does not cause this License to apply to the other
231
+parts of the aggregate.
232
+
233
+  6. Conveying Non-Source Forms.
234
+
235
+  You may convey a covered work in object code form under the terms
236
+of sections 4 and 5, provided that you also convey the
237
+machine-readable Corresponding Source under the terms of this License,
238
+in one of these ways:
239
+
240
+    a) Convey the object code in, or embodied in, a physical product
241
+    (including a physical distribution medium), accompanied by the
242
+    Corresponding Source fixed on a durable physical medium
243
+    customarily used for software interchange.
244
+
245
+    b) Convey the object code in, or embodied in, a physical product
246
+    (including a physical distribution medium), accompanied by a
247
+    written offer, valid for at least three years and valid for as
248
+    long as you offer spare parts or customer support for that product
249
+    model, to give anyone who possesses the object code either (1) a
250
+    copy of the Corresponding Source for all the software in the
251
+    product that is covered by this License, on a durable physical
252
+    medium customarily used for software interchange, for a price no
253
+    more than your reasonable cost of physically performing this
254
+    conveying of source, or (2) access to copy the
255
+    Corresponding Source from a network server at no charge.
256
+
257
+    c) Convey individual copies of the object code with a copy of the
258
+    written offer to provide the Corresponding Source.  This
259
+    alternative is allowed only occasionally and noncommercially, and
260
+    only if you received the object code with such an offer, in accord
261
+    with subsection 6b.
262
+
263
+    d) Convey the object code by offering access from a designated
264
+    place (gratis or for a charge), and offer equivalent access to the
265
+    Corresponding Source in the same way through the same place at no
266
+    further charge.  You need not require recipients to copy the
267
+    Corresponding Source along with the object code.  If the place to
268
+    copy the object code is a network server, the Corresponding Source
269
+    may be on a different server (operated by you or a third party)
270
+    that supports equivalent copying facilities, provided you maintain
271
+    clear directions next to the object code saying where to find the
272
+    Corresponding Source.  Regardless of what server hosts the
273
+    Corresponding Source, you remain obligated to ensure that it is
274
+    available for as long as needed to satisfy these requirements.
275
+
276
+    e) Convey the object code using peer-to-peer transmission, provided
277
+    you inform other peers where the object code and Corresponding
278
+    Source of the work are being offered to the general public at no
279
+    charge under subsection 6d.
280
+
281
+  A separable portion of the object code, whose source code is excluded
282
+from the Corresponding Source as a System Library, need not be
283
+included in conveying the object code work.
284
+
285
+  A "User Product" is either (1) a "consumer product", which means any
286
+tangible personal property which is normally used for personal, family,
287
+or household purposes, or (2) anything designed or sold for incorporation
288
+into a dwelling.  In determining whether a product is a consumer product,
289
+doubtful cases shall be resolved in favor of coverage.  For a particular
290
+product received by a particular user, "normally used" refers to a
291
+typical or common use of that class of product, regardless of the status
292
+of the particular user or of the way in which the particular user
293
+actually uses, or expects or is expected to use, the product.  A product
294
+is a consumer product regardless of whether the product has substantial
295
+commercial, industrial or non-consumer uses, unless such uses represent
296
+the only significant mode of use of the product.
297
+
298
+  "Installation Information" for a User Product means any methods,
299
+procedures, authorization keys, or other information required to install
300
+and execute modified versions of a covered work in that User Product from
301
+a modified version of its Corresponding Source.  The information must
302
+suffice to ensure that the continued functioning of the modified object
303
+code is in no case prevented or interfered with solely because
304
+modification has been made.
305
+
306
+  If you convey an object code work under this section in, or with, or
307
+specifically for use in, a User Product, and the conveying occurs as
308
+part of a transaction in which the right of possession and use of the
309
+User Product is transferred to the recipient in perpetuity or for a
310
+fixed term (regardless of how the transaction is characterized), the
311
+Corresponding Source conveyed under this section must be accompanied
312
+by the Installation Information.  But this requirement does not apply
313
+if neither you nor any third party retains the ability to install
314
+modified object code on the User Product (for example, the work has
315
+been installed in ROM).
316
+
317
+  The requirement to provide Installation Information does not include a
318
+requirement to continue to provide support service, warranty, or updates
319
+for a work that has been modified or installed by the recipient, or for
320
+the User Product in which it has been modified or installed.  Access to a
321
+network may be denied when the modification itself materially and
322
+adversely affects the operation of the network or violates the rules and
323
+protocols for communication across the network.
324
+
325
+  Corresponding Source conveyed, and Installation Information provided,
326
+in accord with this section must be in a format that is publicly
327
+documented (and with an implementation available to the public in
328
+source code form), and must require no special password or key for
329
+unpacking, reading or copying.
330
+
331
+  7. Additional Terms.
332
+
333
+  "Additional permissions" are terms that supplement the terms of this
334
+License by making exceptions from one or more of its conditions.
335
+Additional permissions that are applicable to the entire Program shall
336
+be treated as though they were included in this License, to the extent
337
+that they are valid under applicable law.  If additional permissions
338
+apply only to part of the Program, that part may be used separately
339
+under those permissions, but the entire Program remains governed by
340
+this License without regard to the additional permissions.
341
+
342
+  When you convey a copy of a covered work, you may at your option
343
+remove any additional permissions from that copy, or from any part of
344
+it.  (Additional permissions may be written to require their own
345
+removal in certain cases when you modify the work.)  You may place
346
+additional permissions on material, added by you to a covered work,
347
+for which you have or can give appropriate copyright permission.
348
+
349
+  Notwithstanding any other provision of this License, for material you
350
+add to a covered work, you may (if authorized by the copyright holders of
351
+that material) supplement the terms of this License with terms:
352
+
353
+    a) Disclaiming warranty or limiting liability differently from the
354
+    terms of sections 15 and 16 of this License; or
355
+
356
+    b) Requiring preservation of specified reasonable legal notices or
357
+    author attributions in that material or in the Appropriate Legal
358
+    Notices displayed by works containing it; or
359
+
360
+    c) Prohibiting misrepresentation of the origin of that material, or
361
+    requiring that modified versions of such material be marked in
362
+    reasonable ways as different from the original version; or
363
+
364
+    d) Limiting the use for publicity purposes of names of licensors or
365
+    authors of the material; or
366
+
367
+    e) Declining to grant rights under trademark law for use of some
368
+    trade names, trademarks, or service marks; or
369
+
370
+    f) Requiring indemnification of licensors and authors of that
371
+    material by anyone who conveys the material (or modified versions of
372
+    it) with contractual assumptions of liability to the recipient, for
373
+    any liability that these contractual assumptions directly impose on
374
+    those licensors and authors.
375
+
376
+  All other non-permissive additional terms are considered "further
377
+restrictions" within the meaning of section 10.  If the Program as you
378
+received it, or any part of it, contains a notice stating that it is
379
+governed by this License along with a term that is a further
380
+restriction, you may remove that term.  If a license document contains
381
+a further restriction but permits relicensing or conveying under this
382
+License, you may add to a covered work material governed by the terms
383
+of that license document, provided that the further restriction does
384
+not survive such relicensing or conveying.
385
+
386
+  If you add terms to a covered work in accord with this section, you
387
+must place, in the relevant source files, a statement of the
388
+additional terms that apply to those files, or a notice indicating
389
+where to find the applicable terms.
390
+
391
+  Additional terms, permissive or non-permissive, may be stated in the
392
+form of a separately written license, or stated as exceptions;
393
+the above requirements apply either way.
394
+
395
+  8. Termination.
396
+
397
+  You may not propagate or modify a covered work except as expressly
398
+provided under this License.  Any attempt otherwise to propagate or
399
+modify it is void, and will automatically terminate your rights under
400
+this License (including any patent licenses granted under the third
401
+paragraph of section 11).
402
+
403
+  However, if you cease all violation of this License, then your
404
+license from a particular copyright holder is reinstated (a)
405
+provisionally, unless and until the copyright holder explicitly and
406
+finally terminates your license, and (b) permanently, if the copyright
407
+holder fails to notify you of the violation by some reasonable means
408
+prior to 60 days after the cessation.
409
+
410
+  Moreover, your license from a particular copyright holder is
411
+reinstated permanently if the copyright holder notifies you of the
412
+violation by some reasonable means, this is the first time you have
413
+received notice of violation of this License (for any work) from that
414
+copyright holder, and you cure the violation prior to 30 days after
415
+your receipt of the notice.
416
+
417
+  Termination of your rights under this section does not terminate the
418
+licenses of parties who have received copies or rights from you under
419
+this License.  If your rights have been terminated and not permanently
420
+reinstated, you do not qualify to receive new licenses for the same
421
+material under section 10.
422
+
423
+  9. Acceptance Not Required for Having Copies.
424
+
425
+  You are not required to accept this License in order to receive or
426
+run a copy of the Program.  Ancillary propagation of a covered work
427
+occurring solely as a consequence of using peer-to-peer transmission
428
+to receive a copy likewise does not require acceptance.  However,
429
+nothing other than this License grants you permission to propagate or
430
+modify any covered work.  These actions infringe copyright if you do
431
+not accept this License.  Therefore, by modifying or propagating a
432
+covered work, you indicate your acceptance of this License to do so.
433
+
434
+  10. Automatic Licensing of Downstream Recipients.
435
+
436
+  Each time you convey a covered work, the recipient automatically
437
+receives a license from the original licensors, to run, modify and
438
+propagate that work, subject to this License.  You are not responsible
439
+for enforcing compliance by third parties with this License.
440
+
441
+  An "entity transaction" is a transaction transferring control of an
442
+organization, or substantially all assets of one, or subdividing an
443
+organization, or merging organizations.  If propagation of a covered
444
+work results from an entity transaction, each party to that
445
+transaction who receives a copy of the work also receives whatever
446
+licenses to the work the party's predecessor in interest had or could
447
+give under the previous paragraph, plus a right to possession of the
448
+Corresponding Source of the work from the predecessor in interest, if
449
+the predecessor has it or can get it with reasonable efforts.
450
+
451
+  You may not impose any further restrictions on the exercise of the
452
+rights granted or affirmed under this License.  For example, you may
453
+not impose a license fee, royalty, or other charge for exercise of
454
+rights granted under this License, and you may not initiate litigation
455
+(including a cross-claim or counterclaim in a lawsuit) alleging that
456
+any patent claim is infringed by making, using, selling, offering for
457
+sale, or importing the Program or any portion of it.
458
+
459
+  11. Patents.
460
+
461
+  A "contributor" is a copyright holder who authorizes use under this
462
+License of the Program or a work on which the Program is based.  The
463
+work thus licensed is called the contributor's "contributor version".
464
+
465
+  A contributor's "essential patent claims" are all patent claims
466
+owned or controlled by the contributor, whether already acquired or
467
+hereafter acquired, that would be infringed by some manner, permitted
468
+by this License, of making, using, or selling its contributor version,
469
+but do not include claims that would be infringed only as a
470
+consequence of further modification of the contributor version.  For
471
+purposes of this definition, "control" includes the right to grant
472
+patent sublicenses in a manner consistent with the requirements of
473
+this License.
474
+
475
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
476
+patent license under the contributor's essential patent claims, to
477
+make, use, sell, offer for sale, import and otherwise run, modify and
478
+propagate the contents of its contributor version.
479
+
480
+  In the following three paragraphs, a "patent license" is any express
481
+agreement or commitment, however denominated, not to enforce a patent
482
+(such as an express permission to practice a patent or covenant not to
483
+sue for patent infringement).  To "grant" such a patent license to a
484
+party means to make such an agreement or commitment not to enforce a
485
+patent against the party.
486
+
487
+  If you convey a covered work, knowingly relying on a patent license,
488
+and the Corresponding Source of the work is not available for anyone
489
+to copy, free of charge and under the terms of this License, through a
490
+publicly available network server or other readily accessible means,
491
+then you must either (1) cause the Corresponding Source to be so
492
+available, or (2) arrange to deprive yourself of the benefit of the
493
+patent license for this particular work, or (3) arrange, in a manner
494
+consistent with the requirements of this License, to extend the patent
495
+license to downstream recipients.  "Knowingly relying" means you have
496
+actual knowledge that, but for the patent license, your conveying the
497
+covered work in a country, or your recipient's use of the covered work
498
+in a country, would infringe one or more identifiable patents in that
499
+country that you have reason to believe are valid.
500
+
501
+  If, pursuant to or in connection with a single transaction or
502
+arrangement, you convey, or propagate by procuring conveyance of, a
503
+covered work, and grant a patent license to some of the parties
504
+receiving the covered work authorizing them to use, propagate, modify
505
+or convey a specific copy of the covered work, then the patent license
506
+you grant is automatically extended to all recipients of the covered
507
+work and works based on it.
508
+
509
+  A patent license is "discriminatory" if it does not include within
510
+the scope of its coverage, prohibits the exercise of, or is
511
+conditioned on the non-exercise of one or more of the rights that are
512
+specifically granted under this License.  You may not convey a covered
513
+work if you are a party to an arrangement with a third party that is
514
+in the business of distributing software, under which you make payment
515
+to the third party based on the extent of your activity of conveying
516
+the work, and under which the third party grants, to any of the
517
+parties who would receive the covered work from you, a discriminatory
518
+patent license (a) in connection with copies of the covered work
519
+conveyed by you (or copies made from those copies), or (b) primarily
520
+for and in connection with specific products or compilations that
521
+contain the covered work, unless you entered into that arrangement,
522
+or that patent license was granted, prior to 28 March 2007.
523
+
524
+  Nothing in this License shall be construed as excluding or limiting
525
+any implied license or other defenses to infringement that may
526
+otherwise be available to you under applicable patent law.
527
+
528
+  12. No Surrender of Others' Freedom.
529
+
530
+  If conditions are imposed on you (whether by court order, agreement or
531
+otherwise) that contradict the conditions of this License, they do not
532
+excuse you from the conditions of this License.  If you cannot convey a
533
+covered work so as to satisfy simultaneously your obligations under this
534
+License and any other pertinent obligations, then as a consequence you may
535
+not convey it at all.  For example, if you agree to terms that obligate you
536
+to collect a royalty for further conveying from those to whom you convey
537
+the Program, the only way you could satisfy both those terms and this
538
+License would be to refrain entirely from conveying the Program.
539
+
540
+  13. Remote Network Interaction; Use with the GNU General Public License.
541
+
542
+  Notwithstanding any other provision of this License, if you modify the
543
+Program, your modified version must prominently offer all users
544
+interacting with it remotely through a computer network (if your version
545
+supports such interaction) an opportunity to receive the Corresponding
546
+Source of your version by providing access to the Corresponding Source
547
+from a network server at no charge, through some standard or customary
548
+means of facilitating copying of software.  This Corresponding Source
549
+shall include the Corresponding Source for any work covered by version 3
550
+of the GNU General Public License that is incorporated pursuant to the
551
+following paragraph.
552
+
553
+  Notwithstanding any other provision of this License, you have
554
+permission to link or combine any covered work with a work licensed
555
+under version 3 of the GNU General Public License into a single
556
+combined work, and to convey the resulting work.  The terms of this
557
+License will continue to apply to the part which is the covered work,
558
+but the work with which it is combined will remain governed by version
559
+3 of the GNU General Public License.
560
+
561
+  14. Revised Versions of this License.
562
+
563
+  The Free Software Foundation may publish revised and/or new versions of
564
+the GNU Affero General Public License from time to time.  Such new versions
565
+will be similar in spirit to the present version, but may differ in detail to
566
+address new problems or concerns.
567
+
568
+  Each version is given a distinguishing version number.  If the
569
+Program specifies that a certain numbered version of the GNU Affero General
570
+Public License "or any later version" applies to it, you have the
571
+option of following the terms and conditions either of that numbered
572
+version or of any later version published by the Free Software
573
+Foundation.  If the Program does not specify a version number of the
574
+GNU Affero General Public License, you may choose any version ever published
575
+by the Free Software Foundation.
576
+
577
+  If the Program specifies that a proxy can decide which future
578
+versions of the GNU Affero General Public License can be used, that proxy's
579
+public statement of acceptance of a version permanently authorizes you
580
+to choose that version for the Program.
581
+
582
+  Later license versions may give you additional or different
583
+permissions.  However, no additional obligations are imposed on any
584
+author or copyright holder as a result of your choosing to follow a
585
+later version.
586
+
587
+  15. Disclaimer of Warranty.
588
+
589
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
590
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
591
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
592
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
593
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
594
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
595
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
596
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
597
+
598
+  16. Limitation of Liability.
599
+
600
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
601
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
602
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
603
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
604
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
605
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
606
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
607
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
608
+SUCH DAMAGES.
609
+
610
+  17. Interpretation of Sections 15 and 16.
611
+
612
+  If the disclaimer of warranty and limitation of liability provided
613
+above cannot be given local legal effect according to their terms,
614
+reviewing courts shall apply local law that most closely approximates
615
+an absolute waiver of all civil liability in connection with the
616
+Program, unless a warranty or assumption of liability accompanies a
617
+copy of the Program in return for a fee.
618
+
619
+                     END OF TERMS AND CONDITIONS
620
+
621
+            How to Apply These Terms to Your New Programs
622
+
623
+  If you develop a new program, and you want it to be of the greatest
624
+possible use to the public, the best way to achieve this is to make it
625
+free software which everyone can redistribute and change under these terms.
626
+
627
+  To do so, attach the following notices to the program.  It is safest
628
+to attach them to the start of each source file to most effectively
629
+state the exclusion of warranty; and each file should have at least
630
+the "copyright" line and a pointer to where the full notice is found.
631
+
632
+    <one line to give the program's name and a brief idea of what it does.>
633
+    Copyright (C) <year>  <name of author>
634
+
635
+    This program is free software: you can redistribute it and/or modify
636
+    it under the terms of the GNU Affero General Public License as published
637
+    by the Free Software Foundation, either version 3 of the License, or
638
+    (at your option) any later version.
639
+
640
+    This program is distributed in the hope that it will be useful,
641
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
642
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
643
+    GNU Affero General Public License for more details.
644
+
645
+    You should have received a copy of the GNU Affero General Public License
646
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
647
+
648
+Also add information on how to contact you by electronic and paper mail.
649
+
650
+  If your software can interact with users remotely through a computer
651
+network, you should also make sure that it provides a way for users to
652
+get its source.  For example, if your program is a web application, its
653
+interface could display a "Source" link that leads users to an archive
654
+of the code.  There are many ways you could offer source, and different
655
+solutions will be better for different programs; see section 13 for the
656
+specific requirements.
657
+
658
+  You should also get your employer (if you work as a programmer) or school,
659
+if any, to sign a "copyright disclaimer" for the program, if necessary.
660
+For more information on this, and how to apply and follow the GNU AGPL, see
661
+<https://www.gnu.org/licenses/>.

+ 145 - 0
Shuffle/README.md

1
+<h1 align="center">
2
+
3
+[![Shuffle Logo](https://github.com/Shuffle/Shuffle/blob/main/frontend/public/images/Shuffle_logo_new.png)](https://shuffler.io)
4
+
5
+Shuffle Automation
6
+
7
+</h1><h4 align="center">
8
+
9
+  <div>
10
+    <p>Deploy<p/>
11
+    <a
12
+    href="https://console.cloud.google.com/marketplace/product/shuffle-public/shuffle"
13
+    target="_blank"
14
+    style="text-decoration: none; display: flex; align-items: center; gap: 8px;"
15
+  >
16
+    <img
17
+      src="https://upload.wikimedia.org/wikipedia/commons/5/51/Google_Cloud_logo.svg"
18
+      height="20"
19
+      alt="Google Cloud Platform"
20
+    />
21
+  </a>
22
+  </div>
23
+
24
+
25
+[Shuffle](https://shuffler.io) is an open source automation platform, built for and by the security professionals. Security operations is complex, but it doesn't have to be. Built to work well with MSSP's and other service providers in mind.
26
+
27
+[ Get training ](https://shuffler.io/training)
28
+[_Key Features_](https://shuffler.io/docs/features) —
29
+[_Community & Support_](https://discord.gg/B2CBzUm) —
30
+[ Get training ](https://shuffler.io/training) -
31
+[_Documentation_](https://shuffler.io/docs) —
32
+[_Getting Started_](https://shuffler.io/docs/getting_started) —
33
+[_Development_](https://github.com/shuffle/Shuffle/blob/master/.github/CONTRIBUTING.md) 
34
+[ Set up a demo call ](https://shuffler.io/contact)
35
+
36
+Follow us on Twitter at [@shuffleio](https://twitter.com/shuffleio).
37
+
38
+
39
+</h4>
40
+
41
+![Example Shuffle webhook integration](https://github.com/shuffle/Shuffle/blob/main/frontend/src/assets/img/github_shuffle_img.png)
42
+
43
+## Deployment
44
+
45
+**Shuffle can be deployed using the following cloud marketplaces:**
46
+
47
+## Try it
48
+* Self-hosted: Check out the [installation guide](https://github.com/shuffle/shuffle/blob/master/.github/install-guide.md)
49
+* Cloud: Register at https://shuffler.io/register and get cooking
50
+
51
+Please consider [sponsoring](https://github.com/sponsors/frikky) the project if you want to see more rapid development.
52
+
53
+## Support
54
+* [Discord](https://discord.gg/B2CBzUm)
55
+* [Twitter](https://twitter.com/shuffleio)
56
+* [Email](mailto:frikky@shuffler.io)
57
+* [Open issue](https://github.com/shuffle/Shuffle/issues/new)
58
+* [Shuffler.io](https://shuffler.io/contact)
59
+
60
+## Blogposts
61
+* [1. Introducing Shuffle](https://medium.com/security-operation-capybara/introducing-shuffle-an-open-source-soar-platform-part-1-58a529de7d12)
62
+* [2. Getting started with Shuffle](https://medium.com/security-operation-capybara/getting-started-with-shuffle-an-open-source-soar-platform-part-2-1d7c67a64244)
63
+* [3. Integrating Shuffle with Virustotal and TheHive](https://medium.com/@Frikkylikeme/integrating-shuffle-with-virustotal-and-thehive-open-source-soar-part-3-8e2e0d3396a9)
64
+* [4. Real-time executions with TheHive, Cortex and MISP](https://medium.com/@Frikkylikeme/indicators-and-webhooks-with-thehive-cortex-and-misp-open-source-soar-part-4-f70cde942e59)
65
+
66
+## Documentation
67
+[Documentation](https://shuffler.io/docs) can be found on [https://shuffler.io/docs](https://shuffler.io/docs) and is written here: [https://github.com/shuffle/shuffle-docs](https://github.com/shuffle/shuffle-docs).
68
+
69
+### Setting up a local development environment
70
+
71
+Please follow the steps mentioned [here](https://github.com/Shuffle/Shuffle/blob/main/.github/install-guide.md#local-development-installation)!
72
+
73
+## Related repositories
74
+* OpenAPI apps: [https://github.com/shuffle/security-openapis](https://github.com/shuffle/security-openapis)
75
+* Documentation: [https://github.com/shuffle/shuffle-docs](https://github.com/shuffle/shuffle-docs)
76
+* Workflows: [https://github.com/shuffle/shuffle-workflows](https://github.com/shuffle/shuffle-workflows)
77
+* Python apps: [https://github.com/shuffle/shuffle-apps](https://github.com/shuffle/python-apps)
78
+
79
+## Features
80
+* Simple, feature rich [workflow editor](https://shuffler.io/docs/workflows)
81
+* App creator using [OpenAPI](https://github.com/shuffle/OpenAPI-security-definitions)
82
+* Premade apps for your security tools
83
+* Organization and sub-organization control
84
+* Hybrid resource sharing with shuffler.io (optional)
85
+
86
+## Website
87
+[https://shuffler.io](https://shuffler.io)
88
+
89
+## Contributing
90
+We want to make the world of cybersecurity more accessible and need all the help we can get. Send an email to [support@shuffler](mailto:support@shuffler.io) and we'll make sure to give you any training you may need.
91
+
92
+These are the main areas to contribute in:
93
+* Frontend (ReactJS)
94
+* Backend (Golang)
95
+* App Creation (Python & GUI w/OpenAPI)
96
+* Documentation (Markdown)
97
+* Workflow creation (GUI & Conceptualizing) 
98
+* Content Creation (Blogs, videos etc) 
99
+
100
+Contributing guidelines are outlined [here](https://github.com/shuffle/Shuffle/blob/master/.github/CONTRIBUTING.md).
101
+
102
+## Contributors 
103
+![ICPL logo](https://github.com/Shuffle/Shuffle/blob/main/frontend/src/assets/img/icpl_logo.png)
104
+
105
+**Shuffle**
106
+<a href="https://github.com/shuffle/shuffle/graphs/contributors">
107
+  <img src="https://contrib.rocks/image?repo=shuffle/shuffle" />
108
+</a>
109
+
110
+[**App magicians**](https://github.com/shuffle/shuffle-apps)
111
+<a href="https://github.com/shuffle/shuffle-apps/graphs/contributors">
112
+  <img src="https://contrib.rocks/image?repo=shuffle/shuffle-apps" />
113
+</a>
114
+
115
+
116
+## License
117
+All modular information related to Shuffle will be under MIT (anyone can use it for whatever purpose), with Shuffle itself using AGPLv3. 
118
+
119
+Workflows: MIT
120
+Documentation: MIT
121
+Shuffle backend: AGPLv3 
122
+Apps, specification and App SDK: MIT
123
+
124
+## Architecture
125
+![Shuffle Architecture](https://github.com/shuffle/Shuffle/blob/main/frontend/src/assets/img/shuffle_architecture.png)
126
+
127
+## Security Contributors: Hall of Fame
128
+[Pushpraj](https://github.com/PushprajPatil) - Reported several issues between July and August 2024
129
+
130
+### Repository overview 
131
+Below is the folder structure with a short explanation
132
+```bash
133
+├── README.md				# What you're reading right now
134
+├── backend					# Contains backend related code.
135
+│   ├── go-app 			# The backend golang webserver
136
+│   └── app_sdk			# The SDK used for apps
137
+├── frontend				# Contains frontend code. ReactJS, Material UI and cytoscape
138
+├── functions				# Has execution and extension resources, such as the Wazuh integration
139
+│   ├── onprem				# Code for onprem solutions
140
+│   │   ├── Orborus 	# Distributes execution locations
141
+│   │   ├── Worker		# Runs a workflow
142
+└ docker-compose.yml 	# Used for deployments
143
+```
144
+
145
+[Get in touch](https://shuffler.io/contact), send a mail to [frikky@shuffler.io](mailto:frikky@shuffler.io) or poke me on twitter [@frikkylikeme](https://twitter.com/frikkylikeme)

+ 19 - 0
Shuffle/SECURITY.md

1
+# Security Policy
2
+
3
+## Supported Versions
4
+
5
+Shuffle is now live in version 1.0.0, but we aim to support older version with critical severity security issues, but do advise you to stay up to date with Major versions. 
6
+
7
+| Version | Supported          |
8
+| ------- | ------------------ |
9
+| >=0.9.0   | :white_check_mark: |
10
+| < 0.9.0   | :x:                |
11
+
12
+## Reporting a Vulnerability
13
+
14
+Reporting a vulnerability can either be done to [support@shuffler.io](mailto:support@shuffler.io) or [through the contact page on our website](https://shuffler.io/contact)
15
+
16
+Security.txt: https://shuffler.io/.well-known/security.txt
17
+
18
+When a >medium severity vulnerability is discovered, expect it to be fixed ASAP - please nag us until it is otherwise. Security is a top priority, and we expect and hope you hold us accountable.
19
+In the case it makes sense, we'll further create a security advisory, and publish a new CVE for your new glorious finding.

+ 43 - 0
Shuffle/backend/Dockerfile

1
+FROM golang:1.24 as builder
2
+
3
+# Add files
4
+RUN mkdir /app
5
+RUN mkdir /app_sdk
6
+WORKDIR /app
7
+ADD ./go-app/main.go /app
8
+ADD ./go-app/walkoff.go /app
9
+ADD ./go-app/docker.go /app
10
+
11
+ADD ./go-app/go.mod /app
12
+
13
+# Required files for code generation
14
+RUN wget -O /app_sdk/app_base.py https://raw.githubusercontent.com/Shuffle/app_sdk/refs/heads/main/shuffle_sdk/shuffle_sdk.py
15
+ADD ./app_gen /app_gen
16
+
17
+RUN go mod download
18
+RUN go mod tidy
19
+
20
+# From November 2022, CGO is enabled due to packages
21
+# that we use requiring it. This is a temporary fix
22
+# and makes us HAVE to install libc compatibility packages farther down.
23
+RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o shufflebackend .
24
+
25
+# Certificate build - gets required certs
26
+FROM alpine:latest as certs
27
+RUN apk add --update --no-scripts ca-certificates
28
+
29
+# Sets up the final image
30
+FROM alpine:3.22.2
31
+
32
+# FIXME: Install cgo because CGO_ENABLED=1 during build
33
+RUN apk add --no-cache libc6-compat
34
+RUN apk add --no-cache libstdc++
35
+
36
+COPY --from=builder /app/ /app
37
+COPY --from=builder /app_sdk/ /app_sdk
38
+COPY --from=builder /app_gen/ /app_gen
39
+COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
40
+
41
+WORKDIR /app
42
+EXPOSE 5001
43
+CMD ["./shufflebackend"]

+ 18 - 0
Shuffle/backend/README.md

1
+# Backend 
2
+This folder has all parts necessary for the backend to run locally and in Docker
3
+
4
+## Structure
5
+* go-app: 	The backend. Modify these to edit the backend API.
6
+* database: The datastore database.
7
+* app_sdk: 	The app_sdk for apps. MIT licensed. 
8
+* app_gen: 	Code used when generating docker images. MIT licensed
9
+* tests: 		A bunch of cronscripts. There are no real, good tests yet
10
+
11
+## Development
12
+Shuffle's backend is written in Go, with apps being python (for now). More about local development can be seen in the main README.
13
+
14
+Running the backend:
15
+```bash
16
+cd go-app
17
+go run main.go docker.go walkoff.go
18
+```

+ 21 - 0
Shuffle/backend/app_gen/LICENSE

1
+MIT License
2
+
3
+Copyright (c) 2020 Frikkylikeme
4
+
5
+Permission is hereby granted, free of charge, to any person obtaining a copy
6
+of this software and associated documentation files (the "Software"), to deal
7
+in the Software without restriction, including without limitation the rights
8
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+copies of the Software, and to permit persons to whom the Software is
10
+furnished to do so, subject to the following conditions:
11
+
12
+The above copyright notice and this permission notice shall be included in all
13
+copies or substantial portions of the Software.
14
+
15
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+SOFTWARE.

+ 5 - 0
Shuffle/backend/app_gen/python-lib/README.md

1
+# Generators
2
+This folder contains an attempt at creating apps & similar from python libraries
3
+
4
+## Howto
5
+

+ 26 - 0
Shuffle/backend/app_gen/python-lib/baseline/Dockerfile

1
+FROM frikky/shuffle:app_sdk as base
2
+
3
+# We're going to stage away all of the bloat from the build tools so lets create a builder stage
4
+FROM base as builder
5
+
6
+# Install all alpine build tools needed for our pip installs
7
+RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev git
8
+
9
+# Install all of our pip packages in a single directory that we can copy to our base image later
10
+RUN mkdir /install
11
+WORKDIR /install
12
+COPY requirements.txt /requirements.txt
13
+RUN pip install --no-cache-dir --upgrade --prefix="/install" -r /requirements.txt
14
+
15
+# Switch back to our base image and copy in all of our built packages and source code
16
+FROM base
17
+COPY --from=builder /install /usr/local
18
+COPY src /app
19
+
20
+# Install any binary dependencies needed in our final image
21
+# RUN apk --no-cache add --update my_binary_dependency
22
+RUN apk --no-cache add jq git curl
23
+
24
+# Finally, lets run our app!
25
+WORKDIR /app
26
+CMD ["python", "app.py", "--log-level", "DEBUG"]

+ 14 - 0
Shuffle/backend/app_gen/python-lib/baseline/docker-compose.yml

1
+version: '3.4'
2
+services:
3
+  thehive4py:
4
+    build:
5
+      context: .
6
+      dockerfile: Dockerfile
7
+    env_file:
8
+      - env.txt
9
+    restart: "no"
10
+    deploy:
11
+      mode: replicated
12
+      replicas: 10
13
+      restart_policy:
14
+        condition: none

+ 4 - 0
Shuffle/backend/app_gen/python-lib/baseline/env.txt

1
+REDIS_URI=redis://redis
2
+REDIS_ACTION_RESULT_CH=action-results
3
+REDIS_ACTION_RESULTS_GROUP=action-results-group
4
+APP_NAME=

+ 1 - 0
Shuffle/backend/app_gen/python-lib/baseline/requirements.txt

1
+# No extra requirements needed

+ 525 - 0
Shuffle/backend/app_gen/python-lib/generator.py

1
+# Read a directory
2
+# Find python functions
3
+# Generate yaml
4
+
5
+# FIXME:
6
+# Position, default_value and function in params
7
+
8
+
9
+
10
+# TO ADD:
11
+# from walkoff_app_sdk.app_base import AppBase
12
+# class TheHive(AppBase): <-- Add appbase
13
+# __version__ = version within class
14
+# app_name = app_name in class
15
+# if __name__ == "__main__":
16
+#    asyncio.run(TheHive.run(), debug=True) <-- APPEND SHIT HERE
17
+# async infront of every function?
18
+# Add async library to imports
19
+
20
+# Make wrapper class? <-- within app.py
21
+
22
+
23
+# 1. Generate app.yaml (functions with returns etc)
24
+# 2. Generate app.py (with imports to the original function etc
25
+# 3. Build requirements.txt based on the items necessary
26
+# 4. Check whether it runs?
27
+
28
+import os
29
+import yaml
30
+import jedi
31
+import shutil
32
+
33
+# Testing generator
34
+entrypoint_directory = "thehive4py"
35
+include_requirements = False
36
+if not os.path.exists(entrypoint_directory):
37
+    include_requirements = True 
38
+    print("Requires library in requirements")
39
+    
40
+
41
+source = '''
42
+import %s
43
+%s.
44
+''' % (entrypoint_directory, entrypoint_directory)
45
+splitsource = source.split("\n")
46
+
47
+# Find modules AKA files
48
+def get_modules():
49
+    curline = splitsource[-2]
50
+    print(splitsource, curline)
51
+    entrypoint = jedi.Script(source, line=len(splitsource)-1, column=len(curline))
52
+
53
+    modules = []
54
+    completions = entrypoint.completions()
55
+    for item in completions:
56
+        if item.type != "module":
57
+            continue
58
+        
59
+        
60
+        modules.append(item.name)
61
+
62
+    return modules
63
+
64
+def loop_modules(modules, data):
65
+# Loop modules AKA files - this is garbage but works lmao
66
+    for module in modules:
67
+        modulesplit = list(splitsource)
68
+        modulesplit[2] = "%s%s." % (modulesplit[2], module)
69
+
70
+        #print(modulesplit)
71
+        source = "\n".join(modulesplit) 
72
+        entrypoint = jedi.Script(source, line=len(modulesplit)-1, column=len(modulesplit[2]))
73
+
74
+        # Loop classes in the files
75
+        for classcompletion in entrypoint.completions():
76
+            if classcompletion.type != "class":
77
+                continue
78
+
79
+            if not classcompletion.full_name.startswith(modulesplit[2]):
80
+                continue
81
+
82
+            # Same thing again, but for functions within classes
83
+            # CBA with subclasses etc atm
84
+
85
+            #print(classcompletion.full_name, modulesplit[2])
86
+        
87
+            classplit = list(modulesplit)
88
+            classplit[2] = "%s." % (classcompletion.full_name)
89
+
90
+            #print(modulesplit)
91
+            source = "\n".join(classplit) 
92
+            entrypoint = jedi.Script(source, line=len(classplit)-1, column=len(classplit[2]))
93
+
94
+            # List of functions sorted by their name 
95
+            nameinternalfunctions = []
96
+            for functioncompletion in entrypoint.completions():
97
+                if functioncompletion.type != "function":
98
+                    continue
99
+
100
+                if not functioncompletion.full_name.startswith(classplit[2]):
101
+                    continue
102
+
103
+                nameinternalfunctions.append(functioncompletion)
104
+
105
+            #print(nameinternalfunctions)
106
+
107
+            # List of functions sorted by their line in the file (reversed)
108
+            # CODE USED TO ACTUALLY PRINT THE CODE
109
+
110
+            #prevnumber = 0
111
+            #numberinternalfunctions = sorted(nameinternalfunctions, key=lambda k: k.line, reverse=True) 
112
+            numberinternalfunctions = sorted(nameinternalfunctions, key=lambda k: k.line) 
113
+            prevnumber = 0
114
+
115
+            origparent = "TheHiveApi"
116
+            # Predefined functions? - maybe skip: __init__ 
117
+            skip_functions = ["__init__"]
118
+            skip_parameters = [""]
119
+            cnt = 0
120
+            for item in numberinternalfunctions:
121
+                if item.parent().name != origparent:
122
+                    continue
123
+
124
+                # FIXME - prolly wrong
125
+                if item.name in skip_functions or (item.name.startswith("__") and item.name.endswith("__")):
126
+                    continue
127
+
128
+                # FIXME - remove
129
+                #print(item.get_line_code())
130
+                #if "=" not in item.get_line_code():
131
+                #    continue
132
+
133
+                #if item.docstring() in item.get_line_code():
134
+                #    print("NO DOCSTRING FOR: %s. Skipping!" % item.name)
135
+                #    cnt += 1
136
+                #    continue
137
+
138
+                curfunction = {
139
+                    "name": item.name,
140
+                    "description": "HEY",
141
+                }
142
+
143
+                params = []
144
+                curreturn = {}
145
+
146
+                function = item.docstring().split("\n")[0]
147
+                for line in item.docstring().split("\n"):
148
+                    if not line:
149
+                        continue
150
+
151
+                    linesplit = line.split(" ")
152
+                    try:
153
+                        curname = linesplit[1][:-1]
154
+                    except IndexError as e:
155
+                        print("IndexError: %s. Line: %s" % (e, line))
156
+                        continue
157
+
158
+                    paramfound = False
159
+                    foundindex = 0
160
+                    cnt = 0
161
+                    for param in params:
162
+                        #print(param["name"], curname)
163
+                        if param["name"] == curname:
164
+                            #print("ALREADY EXISTS: %s" % curname)
165
+                            paramfound = True
166
+                            foundindex = cnt
167
+                            break
168
+
169
+                        cnt += 1
170
+
171
+                    # CBA finding a good parser, as that seemed impossible :(
172
+                    # Skipped :return
173
+                    if line.startswith(":param"):
174
+                        if not paramfound:
175
+                            #print("HERE!: %s" % line)
176
+
177
+                            curparam = {}
178
+                            #print(line)
179
+                            curparam["name"] = curname 
180
+                            curparam["description"] = " ".join(linesplit[2:])
181
+                            #print(curparam["description"])
182
+                            if "\r\n" in curparam["description"]:
183
+                                curparam["description"] = " ".join(curparam["description"].split("\r\n"))
184
+                            if "\n" in curparam["description"]:
185
+                                curparam["description"] = " ".join(curparam["description"].split("\n"))
186
+
187
+                            curparam["function"] = function
188
+
189
+                            #curparam["docstring"] = item.docstring() 
190
+                            params.append(curparam)
191
+                    elif line.startswith(":type"):
192
+                        if paramfound:
193
+                            params[foundindex]["schema"] = {}
194
+                            params[foundindex]["schema"]["type"] = " ".join(linesplit[2:])
195
+                            #print(params)
196
+
197
+                            #print(line)
198
+                    elif line.startswith(":rtype"): 
199
+                        curreturn["type"] = " ".join(linesplit[1:])
200
+
201
+
202
+                # Check whether param is required
203
+                # FIXME - remove
204
+                #if len(params) != 0:
205
+                #    print(params)
206
+                #    continue
207
+
208
+                #print(function)
209
+                #print(params)
210
+
211
+                # FIXME - this might crash when missing docstrings
212
+                # FIXME - is also bad splitt (can be written without e.g. spaces
213
+                # This should maybe be done first? idk
214
+                fields = function.split("(")[1][:-1].split(", ")
215
+                if len(params) == 0:
216
+                    # Handle missing docstrings
217
+                    params = []
218
+                    for item in fields:
219
+                        params.append({
220
+                            "name": item,
221
+                            "description": "",
222
+                            "schema": {},
223
+                            "function": function,
224
+                        })
225
+
226
+                cnt = 0
227
+                for param in params:
228
+                    found = False
229
+
230
+                    for field in fields:
231
+                        if param["name"] in field:
232
+                            if "=" in field:
233
+                                param["required"] = False
234
+                                param["default_value"] = field
235
+                            else:
236
+                                param["required"] = True
237
+
238
+                            found = True
239
+                            break
240
+
241
+                    if not param.get("schema"):
242
+                        #print("Defining object schema for %s" % param["name"])
243
+                        param["schema"] = {}
244
+                        param["schema"]["type"] = "object"
245
+
246
+                    param["position"] = cnt
247
+
248
+                    if not found:
249
+                        # FIXME - what here?
250
+                        pass
251
+                        #print("HANDLE NOT FOUND")
252
+                        #print(param)
253
+                        #print(fields)
254
+
255
+                    cnt += 1
256
+
257
+                if len(params) > 0:
258
+                    curfunction["parameters"] = params
259
+
260
+                if not curfunction.get("returns"):
261
+                    curfunction["returns"] = {}
262
+                    curfunction["returns"]["schema"] = {}
263
+                    curfunction["returns"]["schema"]["type"] = "object"
264
+
265
+                #print(curfunction)
266
+                try:
267
+                    print("Finished prepping %s with %d parameters and return %s" % (item.name, len(curfunction["parameters"]), curfunction["returns"]["schema"]["type"]))
268
+                except KeyError as e:
269
+                    print("Error: %s" % e)
270
+                    #print("Finished prepping %s with 0 parameters and return %s" % (item.name, curfunction["returns"]["schema"]["type"]))
271
+                    curfunction["parameters"] = []
272
+                except AttributeError as e:
273
+                    pass
274
+
275
+                try:
276
+                    data["actions"].append(curfunction)
277
+                except KeyError:
278
+                    data["actions"] = []
279
+                    data["actions"].append(curfunction)
280
+
281
+                #return data
282
+
283
+                # FIXME
284
+                #if cnt == breakcnt:
285
+                #    break
286
+
287
+                #cnt += 1
288
+
289
+                # Check if 
290
+
291
+
292
+                # THIS IS TO GET READ THE ACTUAL CODE
293
+                #functioncode = item.get_line_code(after=prevnumber-item.line-1)
294
+                #prevnumber = item.line
295
+
296
+        # break
297
+    return data
298
+
299
+# Generates the base information necessary to make an api.yaml file
300
+def generate_base_yaml(filename, version, appname):
301
+    print("Generating base app for library %s with version %s" % (appname, version))
302
+    data = {
303
+        "walkoff_version": "0.0.1",
304
+        "app_version": version,
305
+        "name": appname,
306
+        "description": "Autogenerated yaml with @Frikkylikeme's generator",
307
+        "contact_info": {
308
+            "name": "@frikkylikeme",
309
+            "url": "https://github.com/frikky",
310
+        }
311
+    }
312
+
313
+    return data
314
+
315
+def generate_app(filepath, data):
316
+
317
+    tbd = [
318
+        "library_path",
319
+        "import_class",
320
+        "required_init"
321
+    ]
322
+
323
+    # FIXME - add to data dynamically and remove
324
+    data["library_path"] = "thehive4py.api"
325
+    data["import_class"] = "TheHiveApi"
326
+    data["required_init"] = {"url": "http://localhost:9000", "principal": "asd"}
327
+
328
+    wrapperstring = ""
329
+    cnt = 0
330
+    # FIXME - only works for strings currently
331
+    for key, value in data["required_init"].items():
332
+        if cnt != len(data["required_init"]):
333
+            wrapperstring += "%s=\"%s\", " % (key, value)
334
+
335
+        cnt += 1
336
+            
337
+    wrapperstring = wrapperstring[:-2]
338
+    wrapper = "self.wrapper = %s(%s)" % (data["import_class"], wrapperstring)
339
+
340
+    name = data["name"]
341
+    if ":" in data["name"]:
342
+        name = data["name"].split(":")[0]
343
+
344
+    if not data.get("actions"):
345
+        print("No actions found for %s in path %s" % (entrypoint_directory, data["library_path"]))
346
+        print("Folder might be missing (or unexported (__init__.py), library not installed (pip) or library action missing")
347
+        exit()
348
+
349
+    functions = []
350
+    for action in data["actions"]:
351
+        internalparamstring = ""
352
+        paramstring = ""
353
+        try:
354
+            for param in action["parameters"]:
355
+                if param["required"] == False:
356
+                    paramstring += "%s, " % (param["default_value"])
357
+                else:
358
+                    paramstring += "%s, " % param["name"]
359
+        except KeyError:
360
+            action["parameters"] = []
361
+
362
+            #internalparamstring += "%s, " % param["name"]
363
+
364
+        paramstring = paramstring[:-2]
365
+        #internalparamstring = internalparamstring[:-2]
366
+
367
+        functionstring = '''    async def %s(%s):
368
+        return self.wrapper.%s(%s)
369
+        ''' % (action["name"], paramstring, action["name"], paramstring)
370
+
371
+        functions.append(functionstring)
372
+
373
+    filedata = '''from walkoff_app_sdk.app_base import AppBase
374
+import asyncio
375
+
376
+from %s import %s
377
+
378
+class %sWrapper(AppBase):
379
+
380
+    __version__ = "%s"
381
+    app_name = "%s"
382
+
383
+    def __init__(self, redis, logger, console_logger=None):
384
+        """
385
+        Each app should have this __init__ to set up Redis and logging.
386
+        :param redis:
387
+        :param logger:
388
+        :param console_logger:
389
+        """
390
+
391
+        super().__init__(redis, logger, console_logger)   
392
+        %s
393
+
394
+%s
395
+
396
+if __name__ == "__main__":
397
+    asyncio.run(%sWrapper.run(), debug=True)
398
+''' % ( \
399
+        data["library_path"],
400
+        data["import_class"],
401
+        name, 
402
+        data["app_version"],
403
+        name, 
404
+        wrapper,
405
+        "\n".join(functions),
406
+        name 
407
+    )
408
+
409
+    # Simple key cleanup
410
+    for item in tbd:
411
+        try:
412
+            del data[item]
413
+        except KeyError:
414
+            pass
415
+
416
+
417
+    tbd_action = []
418
+
419
+    tbd_param = [
420
+        "position",
421
+        "default_value",
422
+        "function"
423
+    ]
424
+
425
+    for action in data["actions"]:
426
+        for param in action["parameters"]: 
427
+            for item in tbd_param:
428
+                try:
429
+                    del param[item]
430
+                except KeyError:
431
+                    pass
432
+
433
+        for item in tbd_action:
434
+            try:
435
+                del action[item]
436
+            except KeyError:
437
+                pass
438
+
439
+    # FIXME - add how to initialize the class
440
+    with open(filepath, "w") as tmp:
441
+        tmp.write(filedata)
442
+
443
+    return data
444
+
445
+def dump_yaml(filename, data):
446
+    with open(filename, 'w') as outfile:
447
+        yaml.dump(data, outfile, default_flow_style=False)
448
+
449
+def build_base_structure(appname, version):
450
+    outputdir = "generated"
451
+    app_path = "%s/%s" % (outputdir, appname)
452
+    filepath = "%s/%s" % (app_path, version)
453
+    srcdir_path = "%s/src" % (filepath)
454
+
455
+    directories = [
456
+        outputdir,
457
+        app_path,
458
+        filepath,
459
+        srcdir_path
460
+    ]
461
+
462
+    for directory in directories:
463
+        try:
464
+            os.mkdir(directory)
465
+        except FileExistsError:
466
+            print("%s already exists. Skipping." % directory)
467
+
468
+    # "docker-compose.yml", 
469
+    # "env.txt",
470
+    filenames = [
471
+        "Dockerfile", 
472
+        "requirements.txt"
473
+    ]
474
+    
475
+    #if strings.
476
+    # include_requirements = False
477
+
478
+    for filename in filenames:
479
+        ret = shutil.copyfile("baseline/%s" % filename, "%s/%s" % (filepath, filename))
480
+        print("Copied baseline/%s." % filename) 
481
+
482
+def move_files(appname, version): 
483
+    applocation = "../../functions/apps/%s" % appname
484
+    if not os.path.exists("../../functions/apps"):
485
+        os.mkdir("../../functions/apps")
486
+
487
+    if not os.path.exists(applocation):
488
+        os.mkdir(applocation)
489
+
490
+    versionlocation = "%s/%s" % (applocation, version)
491
+    if not os.path.exists(versionlocation):
492
+        os.mkdir(versionlocation)
493
+
494
+    shutil.rmtree(versionlocation)
495
+    shutil.move("generated/%s/%s" % (appname, version), versionlocation)
496
+
497
+    print("\nMoved files to %s" % versionlocation)
498
+
499
+
500
+def main():
501
+    appname = entrypoint_directory
502
+    version = "0.0.1"
503
+    output_path = "generated/%s/%s" % (appname, version)
504
+    api_yaml_path = "%s/api.yaml" % (output_path)
505
+    app_python_path = "%s/src/app.py" % (output_path)
506
+
507
+    # Builds the directory structure for the app
508
+    build_base_structure(appname, version)
509
+
510
+    # Generates the yaml based on input library etc
511
+    data = generate_base_yaml(api_yaml_path, version, appname)
512
+    modules = get_modules()
513
+    data = loop_modules(modules, data)
514
+
515
+    # Generates app file  
516
+    data = generate_app(app_python_path, data)
517
+
518
+    # Dumps the yaml to specified directory 
519
+    dump_yaml(api_yaml_path, data)
520
+
521
+    # Move the file to functions/apps repository
522
+    move_files(appname, version)
523
+    
524
+if __name__ == "__main__":
525
+    main()

+ 2 - 0
Shuffle/backend/app_gen/python-lib/requirements.txt

1
+jedi
2
+pyyaml

+ 2 - 0
Shuffle/backend/app_sdk/README.md

1
+## CHANGES 
2
+In November 2024, we moved this to its own repistory: https://github.com/shuffle/app_sdk

+ 14 - 0
Shuffle/backend/build.sh

1
+#!/bin/sh
2
+docker stop shuffle-backend
3
+docker rm shuffle-backend
4
+docker rmi ghcr.io/shuffle/shuffle-backend:nightly
5
+
6
+docker build . -t ghcr.io/shuffle/shuffle-backend:nightly
7
+docker push ghcr.io/shuffle/shuffle-backend:nightly
8
+
9
+echo "Starting server"
10
+#docker run -it \
11
+#	-p 5001:5001 \
12
+#	-v /var/run/docker.sock:/var/run/docker.sock \
13
+#	--env DATASTORE_EMULATOR_HOST=192.168.3.6:8000 \
14
+#	frikky/shuffle:backend	

+ 34 - 0
Shuffle/backend/database/opensearch/docker-compose.yml

1
+version: '3'
2
+services:
3
+  opensearch-node1:
4
+    image: opensearchproject/opensearch:latest
5
+    hostname: shuffle-database
6
+    container_name: shuffle-opensearch
7
+    environment:
8
+      - cluster.name=shuffle-cluster
9
+      - node.name=shuffle-opensearch
10
+      - discovery.seed_hosts=shuffle-opensearch
11
+      - cluster.initial_master_nodes=shuffle-opensearch
12
+      - bootstrap.memory_lock=true # along with the memlock settings below, disables swapping
13
+      - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM
14
+      - cluster.routing.allocation.disk.threshold_enabled=false
15
+      - opendistro_security.disabled=true
16
+    ulimits:
17
+      memlock:
18
+        soft: -1
19
+        hard: -1
20
+      nofile:
21
+        soft: 65536 # maximum number of open files for the OpenSearch user, set to at least 65536 on modern systems
22
+        hard: 65536
23
+    volumes:
24
+      - ~/git/shuffle/shuffle-database:/usr/share/opensearch/data
25
+    ports:
26
+      - 9200:9200
27
+    networks:
28
+      - opensearch-net
29
+
30
+volumes:
31
+  opensearch-data1:
32
+
33
+networks:
34
+  opensearch-net:

+ 20 - 0
Shuffle/backend/go-app/README.md

1
+# Run
2
+go run main.go walkoff.go docker.go
3
+
4
+## Modify
5
+- Make sure it's connected with the latest version of the shuffle-shared library, which is used to get resources from Shuffle
6
+
7
+## Database
8
+- The database is Opensearch and can be modified with the SHUFFLE_OPENSEARCH_URL environment variable. See .env in the root directory for more. This requires Opensearch to be running (typically started from docker-compose.yml)
9
+```
10
+docker-compose up -d
11
+docker stop shuffle-backend
12
+docker stop shuffle-frontend
13
+docker stop shuffle-orborus
14
+```
15
+
16
+## Caching
17
+- To handle caching, it by default runs it in memory of the application itself. If you want to offload this, it can be done using the SHUFFLE_MEMCACHED environment variable, connecting to a memcached instance. 
18
+```
19
+docker run --name shuffle-cache -p 11211:11211 -d memcached -m 1024
20
+```

Plik diff jest za duży
+ 1058 - 0
Shuffle/backend/go-app/docker.go


+ 180 - 0
Shuffle/backend/go-app/go.mod

1
+module shuffle
2
+
3
+go 1.24.0
4
+
5
+toolchain go1.24.3
6
+
7
+//replace github.com/shuffle/shuffle-shared => ../../../shuffle-shared
8
+
9
+//replace github.com/frikky/schemaless => ../../../schemaless
10
+
11
+//replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi
12
+//replace github.com/shuffle/opensearch-go => ../../../opensearch-go
13
+
14
+require (
15
+	cloud.google.com/go/datastore v1.20.0
16
+	cloud.google.com/go/storage v1.55.0
17
+	github.com/basgys/goxml2json v1.1.0
18
+	github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82
19
+	github.com/docker/docker v28.3.3+incompatible
20
+	github.com/frikky/kin-openapi v0.42.0
21
+	github.com/fsouza/go-dockerclient v1.12.1
22
+	github.com/ghodss/yaml v1.0.0
23
+	github.com/go-co-op/gocron v1.37.0
24
+	github.com/go-git/go-billy/v5 v5.6.2
25
+	github.com/go-git/go-git/v5 v5.16.5
26
+	github.com/gorilla/mux v1.8.1
27
+	github.com/h2non/filetype v1.1.3
28
+	github.com/satori/go.uuid v1.2.0
29
+	github.com/shuffle/shuffle-shared v0.9.90
30
+	github.com/shuffle/singul v0.0.26
31
+	golang.org/x/crypto v0.45.0
32
+	google.golang.org/api v0.236.0
33
+	google.golang.org/grpc v1.72.2
34
+	gopkg.in/yaml.v3 v3.0.1
35
+	k8s.io/api v0.34.2
36
+	k8s.io/apimachinery v0.34.2
37
+	k8s.io/client-go v0.34.2
38
+)
39
+
40
+require (
41
+	cel.dev/expr v0.20.0 // indirect
42
+	cloud.google.com/go v0.121.1 // indirect
43
+	cloud.google.com/go/auth v0.16.1 // indirect
44
+	cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
45
+	cloud.google.com/go/compute/metadata v0.7.0 // indirect
46
+	cloud.google.com/go/iam v1.5.2 // indirect
47
+	cloud.google.com/go/monitoring v1.24.2 // indirect
48
+	cloud.google.com/go/scheduler v1.11.7 // indirect
49
+	dario.cat/mergo v1.0.0 // indirect
50
+	github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
51
+	github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 // indirect
52
+	github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 // indirect
53
+	github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 // indirect
54
+	github.com/Masterminds/semver v1.5.0 // indirect
55
+	github.com/Microsoft/go-winio v0.6.2 // indirect
56
+	github.com/ProtonMail/go-crypto v1.1.6 // indirect
57
+	github.com/adrg/strutil v0.3.1 // indirect
58
+	github.com/algolia/algoliasearch-client-go/v3 v3.31.4 // indirect
59
+	github.com/bitly/go-simplejson v0.5.1 // indirect
60
+	github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf // indirect
61
+	github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect
62
+	github.com/cespare/xxhash/v2 v2.3.0 // indirect
63
+	github.com/cloudflare/circl v1.6.1 // indirect
64
+	github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
65
+	github.com/containerd/errdefs v1.0.0 // indirect
66
+	github.com/containerd/errdefs/pkg v0.3.0 // indirect
67
+	github.com/containerd/log v0.1.0 // indirect
68
+	github.com/coreos/go-oidc/v3 v3.17.0 // indirect
69
+	github.com/cyphar/filepath-securejoin v0.4.1 // indirect
70
+	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
71
+	github.com/distribution/reference v0.6.0 // indirect
72
+	github.com/docker/go-connections v0.5.0 // indirect
73
+	github.com/docker/go-units v0.5.0 // indirect
74
+	github.com/emicklei/go-restful/v3 v3.12.2 // indirect
75
+	github.com/emirpasic/gods v1.18.1 // indirect
76
+	github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
77
+	github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
78
+	github.com/felixge/httpsnoop v1.0.4 // indirect
79
+	github.com/frikky/schemaless v0.0.28 // indirect
80
+	github.com/fxamacker/cbor/v2 v2.9.0 // indirect
81
+	github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
82
+	github.com/go-jose/go-jose/v4 v4.1.3 // indirect
83
+	github.com/go-logr/logr v1.4.2 // indirect
84
+	github.com/go-logr/stdr v1.2.2 // indirect
85
+	github.com/go-openapi/jsonpointer v0.21.0 // indirect
86
+	github.com/go-openapi/jsonreference v0.20.2 // indirect
87
+	github.com/go-openapi/swag v0.23.0 // indirect
88
+	github.com/goccy/go-json v0.10.5 // indirect
89
+	github.com/gogo/protobuf v1.3.2 // indirect
90
+	github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
91
+	github.com/golang/protobuf v1.5.4 // indirect
92
+	github.com/google/gnostic-models v0.7.0 // indirect
93
+	github.com/google/go-github/v28 v28.1.1 // indirect
94
+	github.com/google/go-querystring v1.1.0 // indirect
95
+	github.com/google/s2a-go v0.1.9 // indirect
96
+	github.com/google/uuid v1.6.0 // indirect
97
+	github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
98
+	github.com/googleapis/gax-go/v2 v2.14.2 // indirect
99
+	github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
100
+	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
101
+	github.com/josharian/intern v1.0.0 // indirect
102
+	github.com/json-iterator/go v1.1.12 // indirect
103
+	github.com/kevinburke/ssh_config v1.2.0 // indirect
104
+	github.com/klauspost/compress v1.18.0 // indirect
105
+	github.com/mailru/easyjson v0.7.7 // indirect
106
+	github.com/moby/docker-image-spec v1.3.1 // indirect
107
+	github.com/moby/go-archive v0.1.0 // indirect
108
+	github.com/moby/patternmatcher v0.6.0 // indirect
109
+	github.com/moby/sys/sequential v0.6.0 // indirect
110
+	github.com/moby/sys/user v0.4.0 // indirect
111
+	github.com/moby/sys/userns v0.1.0 // indirect
112
+	github.com/moby/term v0.5.2 // indirect
113
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
114
+	github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
115
+	github.com/morikuni/aec v1.0.0 // indirect
116
+	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
117
+	github.com/openai/openai-go/v3 v3.8.1 // indirect
118
+	github.com/opencontainers/go-digest v1.0.0 // indirect
119
+	github.com/opencontainers/image-spec v1.1.1 // indirect
120
+	github.com/osteele/liquid v1.7.0 // indirect
121
+	github.com/osteele/tuesday v1.0.3 // indirect
122
+	github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
123
+	github.com/pjbgf/sha1cd v0.3.2 // indirect
124
+	github.com/pkg/errors v0.9.1 // indirect
125
+	github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
126
+	github.com/robfig/cron/v3 v3.0.1 // indirect
127
+	github.com/sashabaranov/go-openai v1.40.5 // indirect
128
+	github.com/sendgrid/rest v2.6.9+incompatible // indirect
129
+	github.com/sendgrid/sendgrid-go v3.16.1+incompatible // indirect
130
+	github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
131
+	github.com/shuffle/opensearch-go/v4 v4.0.0 // indirect
132
+	github.com/sirupsen/logrus v1.9.3 // indirect
133
+	github.com/skeema/knownhosts v1.3.1 // indirect
134
+	github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
135
+	github.com/spf13/pflag v1.0.6 // indirect
136
+	github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
137
+	github.com/tidwall/gjson v1.18.0 // indirect
138
+	github.com/tidwall/match v1.1.1 // indirect
139
+	github.com/tidwall/pretty v1.2.1 // indirect
140
+	github.com/tidwall/sjson v1.2.5 // indirect
141
+	github.com/x448/float16 v0.8.4 // indirect
142
+	github.com/xanzy/ssh-agent v0.3.3 // indirect
143
+	github.com/zeebo/errs v1.4.0 // indirect
144
+	go.opentelemetry.io/auto/sdk v1.1.0 // indirect
145
+	go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
146
+	go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect
147
+	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
148
+	go.opentelemetry.io/otel v1.36.0 // indirect
149
+	go.opentelemetry.io/otel/metric v1.36.0 // indirect
150
+	go.opentelemetry.io/otel/sdk v1.36.0 // indirect
151
+	go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
152
+	go.opentelemetry.io/otel/trace v1.36.0 // indirect
153
+	go.uber.org/atomic v1.9.0 // indirect
154
+	go.yaml.in/yaml/v2 v2.4.2 // indirect
155
+	go.yaml.in/yaml/v3 v3.0.4 // indirect
156
+	go4.org v0.0.0-20230225012048-214862532bf5 // indirect
157
+	golang.org/x/net v0.47.0 // indirect
158
+	golang.org/x/oauth2 v0.30.0 // indirect
159
+	golang.org/x/sync v0.18.0 // indirect
160
+	golang.org/x/sys v0.38.0 // indirect
161
+	golang.org/x/term v0.37.0 // indirect
162
+	golang.org/x/text v0.31.0 // indirect
163
+	golang.org/x/time v0.11.0 // indirect
164
+	google.golang.org/appengine v1.6.8 // indirect
165
+	google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 // indirect
166
+	google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect
167
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a // indirect
168
+	google.golang.org/protobuf v1.36.6 // indirect
169
+	gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
170
+	gopkg.in/inf.v0 v0.9.1 // indirect
171
+	gopkg.in/warnings.v0 v0.1.2 // indirect
172
+	gopkg.in/yaml.v2 v2.4.0 // indirect
173
+	k8s.io/klog/v2 v2.130.1 // indirect
174
+	k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
175
+	k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
176
+	sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
177
+	sigs.k8s.io/randfill v1.0.0 // indirect
178
+	sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
179
+	sigs.k8s.io/yaml v1.6.0 // indirect
180
+)

+ 693 - 0
Shuffle/backend/go-app/go.sum

1
+cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI=
2
+cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw=
3
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
4
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
5
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
6
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
7
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
8
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
9
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
10
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
11
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
12
+cloud.google.com/go v0.121.1 h1:S3kTQSydxmu1JfLRLpKtxRPA7rSrYPRPEUmL/PavVUw=
13
+cloud.google.com/go v0.121.1/go.mod h1:nRFlrHq39MNVWu+zESP2PosMWA0ryJw8KUBZ2iZpxbw=
14
+cloud.google.com/go/auth v0.16.1 h1:XrXauHMd30LhQYVRHLGvJiYeczweKQXZxsTbV9TiguU=
15
+cloud.google.com/go/auth v0.16.1/go.mod h1:1howDHJ5IETh/LwYs3ZxvlkXF48aSqqJUM+5o02dNOI=
16
+cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
17
+cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
18
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
19
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
20
+cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU=
21
+cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo=
22
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
23
+cloud.google.com/go/datastore v1.20.0 h1:NNpXoyEqIJmZFc0ACcwBEaXnmscUpcG4NkKnbCePmiM=
24
+cloud.google.com/go/datastore v1.20.0/go.mod h1:uFo3e+aEpRfHgtp5pp0+6M0o147KoPaYNaPAKpfh8Ew=
25
+cloud.google.com/go/iam v1.5.2 h1:qgFRAGEmd8z6dJ/qyEchAuL9jpswyODjA2lS+w234g8=
26
+cloud.google.com/go/iam v1.5.2/go.mod h1:SE1vg0N81zQqLzQEwxL2WI6yhetBdbNQuTvIKCSkUHE=
27
+cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
28
+cloud.google.com/go/logging v1.13.0/go.mod h1:36CoKh6KA/M0PbhPKMq6/qety2DCAErbhXT62TuXALA=
29
+cloud.google.com/go/longrunning v0.6.7 h1:IGtfDWHhQCgCjwQjV9iiLnUta9LBCo8R9QmAFsS/PrE=
30
+cloud.google.com/go/longrunning v0.6.7/go.mod h1:EAFV3IZAKmM56TyiE6VAP3VoTzhZzySwI/YI1s/nRsY=
31
+cloud.google.com/go/monitoring v1.24.2 h1:5OTsoJ1dXYIiMiuL+sYscLc9BumrL3CarVLL7dd7lHM=
32
+cloud.google.com/go/monitoring v1.24.2/go.mod h1:x7yzPWcgDRnPEv3sI+jJGBkwl5qINf+6qY4eq0I9B4U=
33
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
34
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
35
+cloud.google.com/go/scheduler v1.11.7 h1:zkMEJ0UbEJ3O7NwEUlKLIp6eXYv1L7wHjbxyxznajKM=
36
+cloud.google.com/go/scheduler v1.11.7/go.mod h1:gqYs8ndLx2M5D0oMJh48aGS630YYvC432tHCnVWN13s=
37
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
38
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
39
+cloud.google.com/go/storage v1.55.0 h1:NESjdAToN9u1tmhVqhXCaCwYBuvEhZLLv0gBr+2znf0=
40
+cloud.google.com/go/storage v1.55.0/go.mod h1:ztSmTTwzsdXe5syLVS0YsbFxXuvEmEyZj7v7zChEmuY=
41
+cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4=
42
+cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI=
43
+dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
44
+dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
45
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
46
+github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
47
+github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
48
+github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
49
+github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
50
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
51
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
52
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0 h1:ErKg/3iS1AKcTkf3yixlZ54f9U1rljCkQyEXWUnIUxc=
53
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.27.0/go.mod h1:yAZHSGnqScoU556rBOVkwLze6WP5N+U11RHuWaGVxwY=
54
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0 h1:fYE9p3esPxA/C0rQ0AHhP0drtPXDRhaWiwg1DPqO7IU=
55
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.51.0/go.mod h1:BnBReJLvVYx2CS/UHOgVz2BXKXD9wsQPxZug20nZhd0=
56
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0 h1:OqVGm6Ei3x5+yZmSJG1Mh2NwHvpVmZ08CB5qJhT9Nuk=
57
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.51.0/go.mod h1:SZiPHWGOOk3bl8tkevxkoiwPgsIl6CwrWcbwjfHZpdM=
58
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0 h1:6/0iUd0xrnX7qt+mLNRwg5c0PGv8wpE8K90ryANQwMI=
59
+github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.51.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0=
60
+github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
61
+github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
62
+github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
63
+github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
64
+github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
65
+github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
66
+github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
67
+github.com/adrg/strutil v0.3.1 h1:OLvSS7CSJO8lBii4YmBt8jiK9QOtB9CzCzwl4Ic/Fz4=
68
+github.com/adrg/strutil v0.3.1/go.mod h1:8h90y18QLrs11IBffcGX3NW/GFBXCMcNg4M7H6MspPA=
69
+github.com/algolia/algoliasearch-client-go/v3 v3.31.4 h1:UJhx6AhZCYf0qZygDz2c1x1+1q2q2sfzsRaQM6yswWk=
70
+github.com/algolia/algoliasearch-client-go/v3 v3.31.4/go.mod h1:i7tLoP7TYDmHX3Q7vkIOL4syVse/k5VJ+k0i8WqFiJk=
71
+github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
72
+github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
73
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
74
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
75
+github.com/basgys/goxml2json v1.1.0 h1:4ln5i4rseYfXNd86lGEB+Vi652IsIXIvggKM/BhUKVw=
76
+github.com/basgys/goxml2json v1.1.0/go.mod h1:wH7a5Np/Q4QoECFIU8zTQlZwZkrilY0itPfecMw41Dw=
77
+github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow=
78
+github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q=
79
+github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf h1:TqhNAT4zKbTdLa62d2HDBFdvgSbIGB3eJE8HqhgiL9I=
80
+github.com/bradfitz/gomemcache v0.0.0-20250403215159-8d39553ac7cf/go.mod h1:r5xuitiExdLAJ09PR7vBVENGvp4ZuTBeWTGtxuX3K+c=
81
+github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 h1:/P9/RL0xgWE+ehnCUUN5h3RpG3dmoMCOONO1CCvq23Y=
82
+github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013/go.mod h1:pccXHIvs3TV/TUqSNyEvF99sxjX2r4FFRIyw6TZY9+w=
83
+github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82 h1:9bAydALqAjBfPHd/eAiJBHnMZUYov8m2PkXVr+YGQeI=
84
+github.com/carlescere/scheduler v0.0.0-20170109141437-ee74d2f83d82/go.mod h1:tyA14J0sA3Hph4dt+AfCjPrYR13+vVodshQSM7km9qw=
85
+github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8=
86
+github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
87
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
88
+github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
89
+github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
90
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
91
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
92
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
93
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
94
+github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
95
+github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
96
+github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk=
97
+github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
98
+github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
99
+github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
100
+github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
101
+github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
102
+github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
103
+github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
104
+github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
105
+github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
106
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
107
+github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
108
+github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
109
+github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
110
+github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
111
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
112
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
113
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
114
+github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
115
+github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
116
+github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
117
+github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
118
+github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
119
+github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
120
+github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
121
+github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
122
+github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
123
+github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
124
+github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
125
+github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
126
+github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
127
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
128
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
129
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
130
+github.com/envoyproxy/go-control-plane v0.13.4 h1:zEqyPVyku6IvWCFwux4x9RxkLOMUL+1vC9xUFv5l2/M=
131
+github.com/envoyproxy/go-control-plane v0.13.4/go.mod h1:kDfuBlDVsSj2MjrLEtRWtHlsWIFcGyB2RMO44Dc5GZA=
132
+github.com/envoyproxy/go-control-plane/envoy v1.32.4 h1:jb83lalDRZSpPWW2Z7Mck/8kXZ5CQAFYVjQcdVIr83A=
133
+github.com/envoyproxy/go-control-plane/envoy v1.32.4/go.mod h1:Gzjc5k8JcJswLjAx1Zm+wSYE20UrLtt7JZMWiWQXQEw=
134
+github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
135
+github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
136
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
137
+github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
138
+github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
139
+github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
140
+github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
141
+github.com/frikky/kin-openapi v0.42.0 h1:d5Z6vnuQ6RnCCPIxZaDL+TH2ODLxT8abytOt+Zh+Kd0=
142
+github.com/frikky/kin-openapi v0.42.0/go.mod h1:ev9OZAw7Bv5p0w93j91++6a1ElPzGcCofst+kmrWsj4=
143
+github.com/frikky/schemaless v0.0.28 h1:gdurMqBwtvY4Y/5pcxn8bdGCJn/eolKGz+c5DcidLkI=
144
+github.com/frikky/schemaless v0.0.28/go.mod h1:m9s+6gALXhA5ZERCrJw+jI2rRtTPNa8mkl4vav9sxnY=
145
+github.com/fsouza/go-dockerclient v1.12.1 h1:FMoLq+Zhv9Oz/rFmu6JWkImfr6CBgZOPcL+bHW4gS0o=
146
+github.com/fsouza/go-dockerclient v1.12.1/go.mod h1:OqsgJJcpCwqyM3JED7TdfM9QVWS5O7jSYwXxYKmOooY=
147
+github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM=
148
+github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
149
+github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
150
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
151
+github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
152
+github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
153
+github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0=
154
+github.com/go-co-op/gocron v1.37.0/go.mod h1:3L/n6BkO7ABj+TrfSVXLRzsP26zmikL4ISkLQ0O8iNY=
155
+github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
156
+github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
157
+github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
158
+github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
159
+github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
160
+github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
161
+github.com/go-git/go-git/v5 v5.16.5 h1:mdkuqblwr57kVfXri5TTH+nMFLNUxIj9Z7F5ykFbw5s=
162
+github.com/go-git/go-git/v5 v5.16.5/go.mod h1:QOMLpNf1qxuSY4StA/ArOdfFR2TrKEjJiye2kel2m+M=
163
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
164
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
165
+github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
166
+github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
167
+github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
168
+github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
169
+github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
170
+github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
171
+github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
172
+github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
173
+github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
174
+github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
175
+github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
176
+github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
177
+github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
178
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
179
+github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
180
+github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
181
+github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
182
+github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
183
+github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
184
+github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
185
+github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
186
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
187
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
188
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
189
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
190
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
191
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
192
+github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
193
+github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
194
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
195
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
196
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
197
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
198
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
199
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
200
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
201
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
202
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
203
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
204
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
205
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
206
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
207
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
208
+github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
209
+github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
210
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
211
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
212
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
213
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
214
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
215
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
216
+github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
217
+github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
218
+github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo=
219
+github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=
220
+github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
221
+github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
222
+github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
223
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
224
+github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
225
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
226
+github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc=
227
+github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0=
228
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
229
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
230
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
231
+github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
232
+github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
233
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
234
+github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
235
+github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
236
+github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
237
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
238
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
239
+github.com/googleapis/enterprise-certificate-proxy v0.3.6 h1:GW/XbdyBFQ8Qe+YAmFU9uHLo7OnF5tL52HFAgMmyrf4=
240
+github.com/googleapis/enterprise-certificate-proxy v0.3.6/go.mod h1:MkHOF77EYAE7qfSuSS9PU6g4Nt4e11cnsDUowfwewLA=
241
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
242
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
243
+github.com/googleapis/gax-go/v2 v2.14.2 h1:eBLnkZ9635krYIPD+ag1USrOAI0Nr0QYF3+/3GqO0k0=
244
+github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmvDQsweuyLV+8+w=
245
+github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
246
+github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
247
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
248
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
249
+github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
250
+github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
251
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
252
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
253
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
254
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
255
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
256
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
257
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
258
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
259
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
260
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
261
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
262
+github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
263
+github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
264
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
265
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
266
+github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
267
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
268
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
269
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
270
+github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
271
+github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
272
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
273
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
274
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
275
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
276
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
277
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
278
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
279
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
280
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
281
+github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
282
+github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
283
+github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
284
+github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
285
+github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
286
+github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
287
+github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
288
+github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
289
+github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
290
+github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
291
+github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
292
+github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
293
+github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
294
+github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
295
+github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
296
+github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
297
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
298
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
299
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
300
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
301
+github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
302
+github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
303
+github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
304
+github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
305
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
306
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
307
+github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM=
308
+github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
309
+github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
310
+github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
311
+github.com/openai/openai-go/v3 v3.8.1 h1:b+YWsmwqXnbpSHWQEntZAkKciBZ5CJXwL68j+l59UDg=
312
+github.com/openai/openai-go/v3 v3.8.1/go.mod h1:UOpNxkqC9OdNXNUfpNByKOtB4jAL0EssQXq5p8gO0Xs=
313
+github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
314
+github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
315
+github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
316
+github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
317
+github.com/osteele/liquid v1.7.0 h1:VsbPSchE5D5S5scylAIvERET4dnCxsO6IDri2oSJ5Dk=
318
+github.com/osteele/liquid v1.7.0/go.mod h1:xU0Z2dn2hOQIEFEWNmeltOmCtfhtoW/2fCyiNQeNG+U=
319
+github.com/osteele/tuesday v1.0.3 h1:SrCmo6sWwSgnvs1bivmXLvD7Ko9+aJvvkmDjB5G4FTU=
320
+github.com/osteele/tuesday v1.0.3/go.mod h1:pREKpE+L03UFuR+hiznj3q7j3qB1rUZ4XfKejwWFF2M=
321
+github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
322
+github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
323
+github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
324
+github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
325
+github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
326
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
327
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
328
+github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
329
+github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
330
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
331
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
332
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
333
+github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
334
+github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
335
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
336
+github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
337
+github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
338
+github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
339
+github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
340
+github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
341
+github.com/sashabaranov/go-openai v1.40.5 h1:SwIlNdWflzR1Rxd1gv3pUg6pwPc6cQ2uMoHs8ai+/NY=
342
+github.com/sashabaranov/go-openai v1.40.5/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
343
+github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
344
+github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
345
+github.com/sendgrid/rest v2.6.9+incompatible h1:1EyIcsNdn9KIisLW50MKwmSRSK+ekueiEMJ7NEoxJo0=
346
+github.com/sendgrid/rest v2.6.9+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
347
+github.com/sendgrid/sendgrid-go v3.16.1+incompatible h1:zWhTmB0Y8XCDzeWIm2/BIt1GjJohAA0p6hVEaDtHWWs=
348
+github.com/sendgrid/sendgrid-go v3.16.1+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
349
+github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
350
+github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
351
+github.com/shuffle/opensearch-go/v4 v4.0.0 h1:Mh85CD1MwOgXiFFYlzS1llnvdqL3CztRdR1ZT/SLIjU=
352
+github.com/shuffle/opensearch-go/v4 v4.0.0/go.mod h1:gVLZKQE5khQWMb68XBtgKrhu78oLGL2zHwAGnFMDwC0=
353
+github.com/shuffle/shuffle-shared v0.9.87 h1:INA1cZ18MKcMs8kaVBJoQqrdNuqLHfge9elJ17hZfVc=
354
+github.com/shuffle/shuffle-shared v0.9.87/go.mod h1:AkXajlWWB16WfWjCw9K7y38L8JKABJzVxcX6KI/J1H4=
355
+github.com/shuffle/singul v0.0.26 h1:P2uZ8YIYQUN4qNfQujoW3Lhod91X6TAfrRHGWiUPNI8=
356
+github.com/shuffle/singul v0.0.26/go.mod h1:S8GszXL+fT2mTnh7j57V0r/tY5Y/mSW8QowQEnADs3k=
357
+github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
358
+github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
359
+github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
360
+github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
361
+github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
362
+github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
363
+github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
364
+github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
365
+github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
366
+github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
367
+github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
368
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
369
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
370
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
371
+github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
372
+github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
373
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
374
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
375
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
376
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
377
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
378
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
379
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
380
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
381
+github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
382
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
383
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
384
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
385
+github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
386
+github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
387
+github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
388
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
389
+github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
390
+github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
391
+github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
392
+github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
393
+github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
394
+github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
395
+github.com/wI2L/jsondiff v0.7.0 h1:1lH1G37GhBPqCfp/lrs91rf/2j3DktX6qYAKZkLuCQQ=
396
+github.com/wI2L/jsondiff v0.7.0/go.mod h1:KAEIojdQq66oJiHhDyQez2x+sRit0vIzC9KeK0yizxM=
397
+github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
398
+github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
399
+github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
400
+github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
401
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
402
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
403
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
404
+github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM=
405
+github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4=
406
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
407
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
408
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
409
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
410
+go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
411
+go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
412
+go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw=
413
+go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k=
414
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw=
415
+go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM=
416
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
417
+go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
418
+go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg=
419
+go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E=
420
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
421
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
422
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0 h1:nRVXXvf78e00EwY6Wp0YII8ww2JVWshZ20HfTlE11AM=
423
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.36.0/go.mod h1:r49hO7CgrxY9Voaj3Xe8pANWtr0Oq916d0XAmOoCZAQ=
424
+go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0 h1:rixTyDGXFxRy1xzhKrotaHy3/KXdPhlWARrCgK+eqUY=
425
+go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.36.0/go.mod h1:dowW6UsM9MKbJq5JTz2AMVp3/5iW5I/TStsk8S+CfHw=
426
+go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE=
427
+go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs=
428
+go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs=
429
+go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY=
430
+go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis=
431
+go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4=
432
+go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
433
+go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
434
+go.opentelemetry.io/proto/otlp v1.6.0 h1:jQjP+AQyTf+Fe7OKj/MfkDrmK4MNVtw2NpXsf9fefDI=
435
+go.opentelemetry.io/proto/otlp v1.6.0/go.mod h1:cicgGehlFuNdgZkcALOCh3VE6K/u2tAjzlRhDwmVpZc=
436
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
437
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
438
+go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
439
+go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
440
+go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
441
+go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
442
+go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
443
+go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
444
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
445
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
446
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
447
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
448
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
449
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
450
+golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
451
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
452
+golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
453
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
454
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
455
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
456
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
457
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
458
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
459
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
460
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
461
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
462
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
463
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
464
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
465
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
466
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
467
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
468
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
469
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
470
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
471
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
472
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
473
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
474
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
475
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
476
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
477
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
478
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
479
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
480
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
481
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
482
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
483
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
484
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
485
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
486
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
487
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
488
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
489
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
490
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
491
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
492
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
493
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
494
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
495
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
496
+golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
497
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
498
+golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
499
+golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
500
+golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
501
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
502
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
503
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
504
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
505
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
506
+golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
507
+golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
508
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
509
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
510
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
511
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
512
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
513
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
514
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
515
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
516
+golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
517
+golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
518
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
519
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
520
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
521
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
522
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
523
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
524
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
525
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
526
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
527
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
528
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
529
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
530
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
531
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
532
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
533
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
534
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
535
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
536
+golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
537
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
538
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
539
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
540
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
541
+golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
542
+golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
543
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
544
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
545
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
546
+golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
547
+golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
548
+golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
549
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
550
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
551
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
552
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
553
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
554
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
555
+golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
556
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
557
+golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
558
+golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
559
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
560
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
561
+golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
562
+golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
563
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
564
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
565
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
566
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
567
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
568
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
569
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
570
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
571
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
572
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
573
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
574
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
575
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
576
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
577
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
578
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
579
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
580
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
581
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
582
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
583
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
584
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
585
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
586
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
587
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
588
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
589
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
590
+golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
591
+golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
592
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
593
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
594
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
595
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
596
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
597
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
598
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
599
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
600
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
601
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
602
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
603
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
604
+google.golang.org/api v0.236.0 h1:CAiEiDVtO4D/Qja2IA9VzlFrgPnK3XVMmRoJZlSWbc0=
605
+google.golang.org/api v0.236.0/go.mod h1:X1WF9CU2oTc+Jml1tiIxGmWFK/UZezdqEu09gcxZAj4=
606
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
607
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
608
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
609
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
610
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
611
+google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
612
+google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
613
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
614
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
615
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
616
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
617
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
618
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
619
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
620
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
621
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
622
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
623
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
624
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
625
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
626
+google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2 h1:1tXaIXCracvtsRxSBsYDiSBN0cuJvM7QYW+MrpIRY78=
627
+google.golang.org/genproto v0.0.0-20250505200425-f936aa4a68b2/go.mod h1:49MsLSx0oWMOZqcpB3uL8ZOkAh1+TndpJ8ONoCBWiZk=
628
+google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0=
629
+google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto=
630
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a h1:v2PbRU4K3llS09c7zodFpNePeamkAwG3mPrAery9VeE=
631
+google.golang.org/genproto/googleapis/rpc v0.0.0-20250528174236-200df99c418a/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
632
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
633
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
634
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
635
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
636
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
637
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
638
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
639
+google.golang.org/grpc v1.72.2 h1:TdbGzwb82ty4OusHWepvFWGLgIbNo1/SUynEN0ssqv8=
640
+google.golang.org/grpc v1.72.2/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM=
641
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
642
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
643
+google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
644
+google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
645
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
646
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
647
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
648
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
649
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
650
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
651
+gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
652
+gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
653
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
654
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
655
+gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
656
+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
657
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
658
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
659
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
660
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
661
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
662
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
663
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
664
+gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
665
+gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
666
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
667
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
668
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
669
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
670
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
671
+k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY=
672
+k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw=
673
+k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4=
674
+k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw=
675
+k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M=
676
+k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE=
677
+k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
678
+k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
679
+k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
680
+k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
681
+k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
682
+k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
683
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
684
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
685
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
686
+sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
687
+sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
688
+sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
689
+sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
690
+sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
691
+sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
692
+sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
693
+sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

Plik diff jest za duży
+ 6160 - 0
Shuffle/backend/go-app/main.go


+ 342 - 0
Shuffle/backend/go-app/main_test.go

1
+package main
2
+
3
+import (
4
+	"github.com/shuffle/shuffle-shared"
5
+
6
+	"bytes"
7
+	"context"
8
+	"log"
9
+	"net/http"
10
+	"net/http/httptest"
11
+	"reflect"
12
+	"runtime"
13
+	"testing"
14
+	"time"
15
+
16
+	"cloud.google.com/go/datastore"
17
+	"cloud.google.com/go/storage"
18
+	"google.golang.org/api/option"
19
+
20
+	"google.golang.org/grpc"
21
+)
22
+
23
+type endpoint struct {
24
+	handler http.HandlerFunc
25
+	path    string
26
+	method  string
27
+	body    []byte
28
+}
29
+
30
+func init() {
31
+	ctx := context.Background()
32
+	dbclient, err := datastore.NewClient(ctx, gceProject, option.WithGRPCDialOption(grpc.WithNoProxy()))
33
+	if err != nil {
34
+		log.Fatalf("[DEBUG] Database client error during init: %s", err)
35
+	}
36
+
37
+	_, err = shuffle.RunInit(*dbclient, storage.Client{}, gceProject, "onprem", true, "elasticsearch", false, 0)
38
+	log.Printf("INIT")
39
+}
40
+
41
+// TestTestAuthenticationRequired tests that the handlers in the `handlers`
42
+// variable returns 401 Unauthorized when called without credentials.
43
+func TestAuthenticationRequired(t *testing.T) {
44
+	handlers := []endpoint{
45
+		{handler: shuffle.HandleNewOutlookRegister, path: "/functions/outlook/register", method: "GET"},
46
+		{handler: shuffle.HandleGetOutlookFolders, path: "/functions/outlook/getFolders", method: "GET"},
47
+		{handler: shuffle.HandleApiGeneration, path: "/api/v1/users/generateapikey", method: "GET"},
48
+		{handler: shuffle.HandleLogin, path: "/api/v1/users/login", method: "POST"}, // prob not this one
49
+		// handleRegister generates nil pointer exception. Not necessary for this anyway.
50
+		//{handler: handleRegister, path: "/api/v1/users/register", method: "POST"},
51
+		{handler: shuffle.HandleGetUsers, path: "/api/v1/users/getusers", method: "GET"},
52
+		{handler: handleInfo, path: "/api/v1/users/getinfo", method: "GET"},
53
+		{handler: shuffle.HandleSettings, path: "/api/v1/users/getsettings", method: "GET"},
54
+		{handler: shuffle.HandleUpdateUser, path: "/api/v1/users/updateuser", method: "PUT"},
55
+		{handler: shuffle.DeleteUser, path: "/api/v1/users/123", method: "DELETE"},
56
+		{handler: shuffle.HandlePasswordChange, path: "/api/v1/users/passwordchange", method: "POST"},
57
+		{handler: shuffle.HandleGetUsers, path: "/api/v1/users", method: "GET"},
58
+		{handler: shuffle.HandleGetEnvironments, path: "/api/v1/getenvironments", method: "GET"},
59
+		{handler: shuffle.HandleSetEnvironments, path: "/api/v1/setenvironments", method: "PUT"},
60
+
61
+		// handleWorkflowQueue generates nil pointer exception
62
+		//{handler: handleWorkflowQueue, path: "/api/v1/streams", method: "POST"},
63
+		// handleGetStreamResults generates nil pointer exception
64
+		//{handler: handleGetStreamResults, path: "/api/v1/streams/results", method: "POST"},
65
+
66
+		{handler: handleAppHotloadRequest, path: "/api/v1/apps/run_hotload", method: "GET"},
67
+		{handler: LoadSpecificApps, path: "/api/v1/apps/get_existing", method: "POST"},
68
+		{handler: shuffle.UpdateWorkflowAppConfig, path: "/api/v1/apps/123", method: "PATCH"},
69
+		{handler: validateAppInput, path: "/api/v1/apps/validate", method: "POST"},
70
+		{handler: shuffle.DeleteWorkflowApp, path: "/api/v1/apps/123", method: "DELETE"},
71
+		{handler: shuffle.GetWorkflowAppConfig, path: "/api/v1/apps/123/config", method: "GET"},
72
+		{handler: getWorkflowApps, path: "/api/v1/apps", method: "GET"},
73
+		{handler: setNewWorkflowApp, path: "/api/v1/apps", method: "PUT"},
74
+		//{handler: shuffle.GetSpecificApps, path: "/api/v1/apps/search", method: "POST"},
75
+
76
+		{handler: shuffle.GetAppAuthentication, path: "/api/v1/apps/authentication", method: "GET"},
77
+		{handler: shuffle.AddAppAuthentication, path: "/api/v1/apps/authentication", method: "PUT"},
78
+		{handler: shuffle.DeleteAppAuthentication, path: "/api/v1/apps/authentication/123", method: "DELETE"},
79
+
80
+		{handler: validateAppInput, path: "/api/v1/workflows/apps/validate", method: "POST"},
81
+		{handler: getWorkflowApps, path: "/api/v1/workflows/apps", method: "GET"},
82
+		{handler: setNewWorkflowApp, path: "/api/v1/workflows/apps", method: "PUT"},
83
+
84
+		{handler: shuffle.GetWorkflows, path: "/api/v1/workflows", method: "GET"},
85
+		{handler: shuffle.SetNewWorkflow, path: "/api/v1/workflows", method: "POST"},
86
+		{handler: handleGetWorkflowqueue, path: "/api/v1/workflows/queue", method: "GET"},
87
+		{handler: handleGetWorkflowqueueConfirm, path: "/api/v1/workflows/queue/confirm", method: "POST"},
88
+		{handler: shuffle.HandleGetSchedules, path: "/api/v1/workflows/schedules", method: "GET"},
89
+		{handler: loadSpecificWorkflows, path: "/api/v1/workflows/download_remote", method: "POST"},
90
+		{handler: executeWorkflow, path: "/api/v1/workflows/123/execute", method: "GET"},
91
+		{handler: scheduleWorkflow, path: "/api/v1/workflows/123/schedule", method: "POST"},
92
+		{handler: stopSchedule, path: "/api/v1/workflows/123/schedule/abc", method: "DELETE"},
93
+		// createOutlookSub generates nil pointer exception
94
+		{handler: shuffle.HandleCreateOutlookSub, path: "/api/v1/workflows/123/outlook", method: "POST"},
95
+		// handleDeleteOutlookSub generates nil pointer exception
96
+		{handler: shuffle.HandleDeleteOutlookSub, path: "/api/v1/workflows/123/outlook/abc", method: "DELETE"},
97
+		{handler: shuffle.GetWorkflowExecutions, path: "/api/v1/workflows/123/executions", method: "GET"},
98
+		{handler: shuffle.AbortExecution, path: "/api/v1/workflows/123/executions/abc/abort", method: "GET"},
99
+		{handler: shuffle.GetSpecificWorkflow, path: "/api/v1/workflows/123", method: "GET"},
100
+		{handler: shuffle.SaveWorkflow, path: "/api/v1/workflows/123", method: "PUT"},
101
+		{handler: deleteWorkflow, path: "/api/v1/workflows/123", method: "DELETE"},
102
+
103
+		{handler: shuffle.HandleNewHook, path: "/api/v1/hooks/new", method: "POST"},
104
+		{handler: handleWebhookCallback, path: "/api/v1/hooks/123", method: "POST"},
105
+		{handler: shuffle.HandleDeleteHook, path: "/api/v1/hooks/123/delete", method: "DELETE"},
106
+
107
+		{handler: shuffle.HandleGetSpecificTrigger, path: "/api/v1/triggers/123", method: "GET"},
108
+		//{handler: shuffle.HandleGetSpecificStats, path: "/api/v1/stats/123", method: "GET"},
109
+
110
+		{handler: verifySwagger, path: "/api/v1/verify_swagger", method: "POST"},
111
+		{handler: verifySwagger, path: "/api/v1/verify_openapi", method: "POST"},
112
+		{handler: shuffle.EchoOpenapiData, path: "/api/v1/get_openapi_uri", method: "POST"},
113
+		{handler: shuffle.EchoOpenapiData, path: "/api/v1/validate_openapi", method: "POST"},
114
+		{handler: shuffle.ValidateSwagger, path: "/api/v1/validate_openapi", method: "POST"},
115
+		{handler: getOpenapi, path: "/api/v1/get_openapi", method: "GET"},
116
+
117
+		//{handler: shuffle.CleanupExecutions, path: "/api/v1/execution_cleanup", method: "GET"},
118
+
119
+		{handler: handleCloudSetup, path: "/api/v1/cloud/setup", method: "POST"},
120
+		{handler: shuffle.HandleGetOrgs, path: "/api/v1/orgs", method: "POST"},
121
+		{handler: shuffle.HandleGetFileContent, path: "/api/v1/files/{fileId}/content", method: "POST", body: []byte("hi")},
122
+	}
123
+
124
+	var err error
125
+	ctx := context.Background()
126
+
127
+	// Most handlers requires database access in order to not crash or cause
128
+	// nil pointer issues.
129
+	// To start a local database instance, run:
130
+	//   docker-compose up database
131
+	// To let the tests know about the database, run:
132
+	//   DATASTORE_EMULATOR_HOST=0.0.0.0:8000 go test
133
+	dbclient, err = datastore.NewClient(ctx, gceProject, option.WithGRPCDialOption(grpc.WithNoProxy()))
134
+	if err != nil {
135
+		t.Fatal(err)
136
+	}
137
+
138
+	dummyBody := bytes.NewBufferString("dummy")
139
+
140
+	for _, e := range handlers {
141
+		log.Printf("Endpoint: %#v", e.path)
142
+		req, err := http.NewRequest(e.method, e.path, dummyBody)
143
+		if err != nil {
144
+			t.Fatal(err)
145
+		}
146
+
147
+		rr := httptest.NewRecorder()
148
+		handler := http.HandlerFunc(e.handler)
149
+
150
+		timeoutHandler := http.TimeoutHandler(handler, 2*time.Second, `Request Timeout.`)
151
+		timeoutHandler.ServeHTTP(rr, req)
152
+
153
+		funcName := getFunctionNameFromFunction(e.handler)
154
+		if status := rr.Code; status != http.StatusUnauthorized {
155
+			t.Errorf("%s handler returned wrong status code: got %v want %v",
156
+				funcName, status, http.StatusUnauthorized)
157
+		}
158
+	}
159
+}
160
+
161
+func TestAuthenticationNotRequired(t *testing.T) {
162
+	// All of these return 200 OK when user not logged in
163
+	handlers := []endpoint{
164
+		{handler: checkAdminLogin, path: "/api/v1/users/checkusers", method: "GET"},
165
+		{handler: shuffle.HandleLogout, path: "/api/v1/users/logout", method: "POST"},
166
+		{handler: shuffle.GetDocList, path: "/api/v1/docs", method: "GET"},
167
+		{handler: shuffle.GetDocs, path: "/api/v1/docs/123", method: "GET"},
168
+		{handler: healthCheckHandler, path: "/api/v1/_ah/health"},
169
+	}
170
+
171
+	for _, e := range handlers {
172
+		log.Printf("Endpoint: %#v", e.path)
173
+		req, err := http.NewRequest(e.method, e.path, nil)
174
+		if err != nil {
175
+			t.Fatal(err)
176
+		}
177
+
178
+		rr := httptest.NewRecorder()
179
+		handler := http.HandlerFunc(e.handler)
180
+
181
+		timeoutHandler := http.TimeoutHandler(handler, 2*time.Second, `Request Timeout.`)
182
+		timeoutHandler.ServeHTTP(rr, req)
183
+
184
+		funcName := getFunctionNameFromFunction(e.handler)
185
+		if status := rr.Code; status != http.StatusOK {
186
+			t.Errorf("%s handler returned wrong status code: got %v want %v",
187
+				funcName, status, http.StatusOK)
188
+		}
189
+	}
190
+}
191
+
192
+// TestCors tests that all endpoints returns the same CORS headers when hit
193
+// with an OPTIONS type request.
194
+// It feels very fragile to test headers like this, especially for the
195
+// "Access-Control-Allow-Origin", but this test should be helpful while
196
+// refactoring the CORS logic into a middleware, and that's the reason this
197
+// test exists right now. It might change after the refactor because our
198
+// requirements might change after the refactor.
199
+func TestCors(t *testing.T) {
200
+	handlers := []endpoint{
201
+		{handler: handleLogin, path: "/api/v1/users/login", method: "POST"}, // prob not this one
202
+
203
+		{handler: shuffle.HandleNewOutlookRegister, path: "/functions/outlook/register", method: "GET"},
204
+		{handler: shuffle.HandleGetOutlookFolders, path: "/functions/outlook/getFolders", method: "GET"},
205
+		{handler: shuffle.HandleApiGeneration, path: "/api/v1/users/generateapikey", method: "GET"},
206
+		// handleRegister generates nil pointer exception
207
+		{handler: handleRegister, path: "/api/v1/users/register", method: "POST"},
208
+		{handler: shuffle.HandleGetUsers, path: "/api/v1/users/getusers", method: "GET"},
209
+		{handler: handleInfo, path: "/api/v1/users/getinfo", method: "GET"},
210
+		{handler: shuffle.HandleSettings, path: "/api/v1/users/getsettings", method: "GET"},
211
+		{handler: shuffle.HandleUpdateUser, path: "/api/v1/users/updateuser", method: "PUT"},
212
+		{handler: shuffle.DeleteUser, path: "/api/v1/users/123", method: "DELETE"},
213
+		// handlePasswordChange generates nil pointer exception
214
+		{handler: shuffle.HandlePasswordChange, path: "/api/v1/users/passwordchange", method: "POST"},
215
+		{handler: shuffle.HandleGetUsers, path: "/api/v1/users", method: "GET"},
216
+		{handler: shuffle.HandleGetEnvironments, path: "/api/v1/getenvironments", method: "GET"},
217
+		{handler: shuffle.HandleSetEnvironments, path: "/api/v1/setenvironments", method: "PUT"},
218
+
219
+		// handleWorkflowQueue generates nil pointer exception
220
+		{handler: handleWorkflowQueue, path: "/api/v1/streams", method: "POST"},
221
+		// handleGetStreamResults generates nil pointer exception
222
+		{handler: handleGetStreamResults, path: "/api/v1/streams/results", method: "POST"},
223
+
224
+		{handler: handleAppHotloadRequest, path: "/api/v1/apps/run_hotload", method: "GET"},
225
+		{handler: LoadSpecificApps, path: "/api/v1/apps/get_existing", method: "POST"},
226
+		{handler: shuffle.UpdateWorkflowAppConfig, path: "/api/v1/apps/123", method: "PATCH"},
227
+		{handler: validateAppInput, path: "/api/v1/apps/validate", method: "POST"},
228
+		{handler: shuffle.DeleteWorkflowApp, path: "/api/v1/apps/123", method: "DELETE"},
229
+		{handler: shuffle.GetWorkflowAppConfig, path: "/api/v1/apps/123/config", method: "GET"},
230
+		{handler: getWorkflowApps, path: "/api/v1/apps", method: "GET"},
231
+		{handler: setNewWorkflowApp, path: "/api/v1/apps", method: "PUT"},
232
+		//{handler: shuffle.GetSpecificApps, path: "/api/v1/apps/search", method: "POST"},
233
+
234
+		{handler: shuffle.GetAppAuthentication, path: "/api/v1/apps/authentication", method: "GET"},
235
+		{handler: shuffle.AddAppAuthentication, path: "/api/v1/apps/authentication", method: "PUT"},
236
+		{handler: shuffle.DeleteAppAuthentication, path: "/api/v1/apps/authentication/123", method: "DELETE"},
237
+
238
+		{handler: validateAppInput, path: "/api/v1/workflows/apps/validate", method: "POST"},
239
+		{handler: getWorkflowApps, path: "/api/v1/workflows/apps", method: "GET"},
240
+		{handler: setNewWorkflowApp, path: "/api/v1/workflows/apps", method: "PUT"},
241
+
242
+		{handler: shuffle.GetWorkflows, path: "/api/v1/workflows", method: "GET"},
243
+		{handler: shuffle.SetNewWorkflow, path: "/api/v1/workflows", method: "POST"},
244
+		{handler: handleGetWorkflowqueue, path: "/api/v1/workflows/queue", method: "GET"},
245
+		{handler: handleGetWorkflowqueueConfirm, path: "/api/v1/workflows/queue/confirm", method: "POST"},
246
+		{handler: shuffle.HandleGetSchedules, path: "/api/v1/workflows/schedules", method: "GET"},
247
+		{handler: loadSpecificWorkflows, path: "/api/v1/workflows/download_remote", method: "POST"},
248
+		{handler: executeWorkflow, path: "/api/v1/workflows/123/execute", method: "GET"},
249
+		{handler: scheduleWorkflow, path: "/api/v1/workflows/123/schedule", method: "POST"},
250
+		{handler: stopSchedule, path: "/api/v1/workflows/123/schedule/abc", method: "DELETE"},
251
+		// createOutlookSub generates nil pointer exception
252
+		{handler: shuffle.HandleCreateOutlookSub, path: "/api/v1/workflows/123/outlook", method: "POST"},
253
+		// handleDeleteOutlookSub generates nil pointer exception
254
+		{handler: shuffle.HandleDeleteOutlookSub, path: "/api/v1/workflows/123/outlook/abc", method: "DELETE"},
255
+		{handler: shuffle.GetWorkflowExecutions, path: "/api/v1/workflows/123/executions", method: "GET"},
256
+		{handler: shuffle.AbortExecution, path: "/api/v1/workflows/123/executions/abc/abort", method: "GET"},
257
+		{handler: shuffle.GetSpecificWorkflow, path: "/api/v1/workflows/123", method: "GET"},
258
+		{handler: shuffle.SaveWorkflow, path: "/api/v1/workflows/123", method: "PUT"},
259
+		{handler: deleteWorkflow, path: "/api/v1/workflows/123", method: "DELETE"},
260
+
261
+		{handler: shuffle.HandleNewHook, path: "/api/v1/hooks/new", method: "POST"},
262
+		{handler: handleWebhookCallback, path: "/api/v1/hooks/123", method: "POST"},
263
+		{handler: shuffle.HandleDeleteHook, path: "/api/v1/hooks/123/delete", method: "DELETE"},
264
+
265
+		{handler: shuffle.HandleGetSpecificTrigger, path: "/api/v1/triggers/123", method: "GET"},
266
+		//{handler: shuffle.HandleGetSpecificStats, path: "/api/v1/stats/123", method: "GET"},
267
+
268
+		{handler: verifySwagger, path: "/api/v1/verify_swagger", method: "POST"},
269
+		{handler: verifySwagger, path: "/api/v1/verify_openapi", method: "POST"},
270
+		{handler: echoOpenapiData, path: "/api/v1/get_openapi_uri", method: "POST"},
271
+		{handler: echoOpenapiData, path: "/api/v1/validate_openapi", method: "POST"},
272
+		{handler: shuffle.ValidateSwagger, path: "/api/v1/validate_openapi", method: "POST"},
273
+		{handler: getOpenapi, path: "/api/v1/get_openapi", method: "GET"},
274
+
275
+		//{handler: shuffle.CleanupExecutions, path: "/api/v1/execution_cleanup", method: "GET"},
276
+
277
+		{handler: handleCloudSetup, path: "/api/v1/cloud/setup", method: "POST"},
278
+		{handler: shuffle.HandleGetOrgs, path: "/api/v1/orgs", method: "POST"},
279
+		{handler: shuffle.HandleGetFileContent, path: "/api/v1/files/{fileId}/content", method: "POST"},
280
+	}
281
+
282
+	//r := initHandlers(context.TODO())
283
+	initHandlers()
284
+
285
+outerLoop:
286
+	for _, e := range handlers {
287
+		log.Printf("Endpoint: %#v", e.path)
288
+		req, err := http.NewRequest("OPTIONS", e.path, nil)
289
+		req.Header.Add("Origin", "http://localhost:3000")
290
+		req.Header.Add("Access-Control-Request-Method", "POST")
291
+		req.Header.Add("Access-Control-Request-Headers", "Content-Type, Accept, X-Requested-With, remember-me")
292
+
293
+		// OPTIONS /resource/foo
294
+		// Access-Control-Request-Method: DELETE
295
+		// Access-Control-Request-Headers: origin, x-requested-with
296
+		// Origin: https://foo.bar.org
297
+
298
+		if err != nil {
299
+			t.Errorf("Failure in OPTIONS setup: %s", err)
300
+			continue
301
+		}
302
+
303
+		rr := httptest.NewRecorder()
304
+
305
+		//timeoutHandler := http.TimeoutHandler(r, 2*time.Second, `Request Timeout`)
306
+		//timeoutHandler.ServeHTTP(rr, req)
307
+
308
+		funcName := getFunctionNameFromFunction(e.handler)
309
+		if status := rr.Code; status != http.StatusOK {
310
+			t.Errorf("%s handler returned wrong status code: got %v want %v",
311
+				funcName, status, http.StatusOK)
312
+			continue
313
+		}
314
+
315
+		want := map[string]string{
316
+			"Vary":                             "Origin",
317
+			"Access-Control-Allow-Headers":     "Content-Type, Accept, X-Requested-With, Remember-Me",
318
+			"Access-Control-Allow-Methods":     "POST",
319
+			"Access-Control-Allow-Credentials": "true",
320
+			"Access-Control-Allow-Origin":      "http://localhost:3000",
321
+		}
322
+
323
+		// Remember to use canonical header name if accessing the headers array
324
+		// directly:
325
+		//   v := r.Header[textproto.CanonicalMIMEHeaderKey("foo")]
326
+		// When using Header().Get(h), h will automatically be converted to canonical format.
327
+
328
+		for key, value := range want {
329
+			got := rr.Header().Get(key)
330
+			if got != value {
331
+				t.Errorf("%s handler returned wrong value for '%s' header: got '%v' want '%v'",
332
+					funcName, key, got, value)
333
+				continue outerLoop
334
+			}
335
+		}
336
+
337
+	}
338
+}
339
+
340
+func getFunctionNameFromFunction(f interface{}) string {
341
+	return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
342
+}

+ 1 - 0
Shuffle/backend/go-app/run

1
+go run main.go walkoff.go docker.go

Plik diff jest za duży
+ 3536 - 0
Shuffle/backend/go-app/walkoff.go


+ 15 - 0
Shuffle/backend/tests/cache.sh

1
+curl -XPOST http://192.168.3.8:5001/api/v1/orgs/6a6a99f5-6630-4f91-88ff-571c9f030ea0/set_cache -H "Authorization: Bearer e663cf93-7f10-4560-bef0-303f14aad982" -d '{
2
+	"workflow_id": "61825389-a125-43a5-9119-97c401e9934b",
3
+	"execution_id": "2c8ca1b7-6658-4742-86a1-105c9467702d",
4
+	"org_id": "6a6a99f5-6630-4f91-88ff-571c9f030ea0",
5
+	"key": "test",
6
+	"value": "THIS IS SOME DATA HELLO"
7
+}'
8
+
9
+curl -XPOST http://192.168.3.8:5001/api/v1/orgs/6a6a99f5-6630-4f91-88ff-571c9f030ea0/get_cache -H "Authorization: Bearer e663cf93-7f10-4560-bef0-303f14aad982" -d '{
10
+	"workflow_id": "61825389-a125-43a5-9119-97c401e9934b",
11
+	"execution_id": "2c8ca1b7-6658-4742-86a1-105c9467702d",
12
+	"org_id": "6a6a99f5-6630-4f91-88ff-571c9f030ea0",
13
+	"key": "test"
14
+}'
15
+

+ 2 - 0
Shuffle/backend/tests/cleanup.sh

1
+
2
+curl http://localhost:5001/api/v1/execution_cleanup -H "Authorization: Bearer e08c6f22-9a55-4557-b008-04388cc51fb0"

+ 5 - 0
Shuffle/backend/tests/dockerpull.sh

1
+#!/bin/sh
2
+#curl -XPOST http://localhost:5001/api/v1/get_docker_image -d '{"name":"frikky/shuffle:testing_1.0.0"}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" --output tarball.tgz
3
+#curl -XPOST http://localhost:5001/api/v1/get_docker_image -d '{"name":"frikky/shuffle:testing_1.0.0"}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" -O -J
4
+#curl -XPOST http://localhost:5001/api/v1/get_docker_image -d '{"name":"frikky/shuffle:testing_1.0.0"}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" --output tarball.tgz
5
+curl -XPOST http://localhost:5001/api/v1/get_docker_image -d '{"name":"frikky/shuffle:shuffle-tools_1.0.0"}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" --output tarball.tgz

+ 26 - 0
Shuffle/backend/tests/execute.sh

1
+#!/bin/sh
2
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
3
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
4
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
5
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
6
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
7
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
8
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
9
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
10
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
11
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
12
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
13
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
14
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
15
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
16
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
17
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
18
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
19
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
20
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
21
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
22
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
23
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
24
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
25
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"
26
+curl -XPOST http://localhost:5001/api/v1/workflows/425efd39-08e7-4390-9387-170c172775f7/execute -d '{"execution_argument":""}' -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"

+ 3 - 0
Shuffle/backend/tests/file_download.sh

1
+# ./b199646b-16d2-456d-9fd6-b9972e929466/2e9d6474-402c-4dcc-bb53-45f638ca18d3/0d676d72-5d53-4803-a6b0-4afb464df828
2
+# org_id / workflow_id / file_id
3
+curl http://192.168.3.6:5001/api/v1/files/0d676d72-5d53-4803-a6b0-4afb464df828/content -H "Authorization: Bearer 093b576f-19ea-4353-b685-362ab50f39f4"

+ 22 - 0
Shuffle/backend/tests/files.sh

1
+#curl http://192.168.3.6:5001/api/v1/files/create -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" -d '{"filename": "file.txt", "org_id": "b199646b-16d2-456d-9fd6-b9972e929466", "workflow_id": "global"}'
2
+#
3
+#curl http://localhost:5001/api/v1/files/create -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" -d '{"filename": "file.txt", "org_id": "b199646b-16d2-456d-9fd6-b9972e929466", "workflow_id": "global"}'
4
+#
5
+#echo
6
+#curl http://localhost:5001/api/v1/apps/upload -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" -F 'shuffle_file=@files.sh'
7
+#
8
+#curl http://localhost:5001/api/v1/files/1915981b-b897-4db1-8a2e-44bc34cead3b/content -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" 
9
+#curl http://localhost:5001/api/v1/files/e19cffe4-e2da-47e9-809e-904f5cb03687 -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" 
10
+#curl -XDELETE http://localhost:5001/api/v1/files/e19cffe4-e2da-47e9-809e-904f5cb03687 -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" 
11
+
12
+	#r.HandleFunc("/api/v1/files/{fileId}/content", handleGetFileContent).Methods("GET", "OPTIONS")
13
+	#r.HandleFunc("/api/v1/files/create", handleCreateFile).Methods("POST", "OPTIONS")
14
+	#r.HandleFunc("/api/v1/files/{fileId}/upload", handleUploadFile).Methods("POST", "OPTIONS")
15
+	#r.HandleFunc("/api/v1/files/{fileId}", handleGetFileMeta).Methods("GET", "OPTIONS")
16
+	#r.HandleFunc("/api/v1/files/{fileId}", handleDeleteFile).Methods("DELETE", "OPTIONS")
17
+
18
+
19
+curl http://localhost:5001/api/v1/files/create -H "Authorization: Bearer 317f5066-395c-414d-aa3d-479cf27f47dd" -d '{"filename": "rule2.yar", "org_id": "292c7e25-40ad-4f05-904f-77d3c7b735e6", "workflow_id": "global", "namespace": "yara"}'
20
+curl http://localhost:5001/api/v1/files/file_eb89e315-eb66-4d76-9df7-530fb003fc84/upload -H "Authorization: Bearer 317f5066-395c-414d-aa3d-479cf27f47dd" -F 'shuffle_file=@upload.sh'
21
+
22
+#curl http://localhost:5001/api/v1/files/namespaces/yara -H "Authorization: Bearer c5b4c827-65ec-47f4-9e8a-234cdba38959" --output rules.zip

Plik diff jest za duży
+ 206 - 0
Shuffle/backend/tests/forparser.py


+ 26 - 0
Shuffle/backend/tests/hooks.sh

1
+#curl localhost:5000/api/v1/hooks
2
+
3
+# Starts a webhook
4
+#curl localhost:5000/api/v1/hooks/d6ef8912e8bd37776e654cbc14c2629c/start
5
+
6
+# Runs a request towards the webhook created
7
+#curl -XPOST localhost:5002/webhook -d '{"helo": "hi"}'
8
+
9
+# Gets the ID of a new hook
10
+
11
+#curl -X PUT localhost:5001/api/v1/hooks/e6f77059e6d469a6c4c314cc06d5a4c0 -d '{"name": "asd", "description": "hola", "type": "webhook", "id": "e6f77059e6d469a6c4c314cc06d5a4c0", "status": "stopped", "info": {"name": "lul", "url": "http://test"}}'
12
+
13
+#curl -X POST localhost:5000/api/v1/hooks/new -d '{"name": "asd", "description": "hola", "type": "webhook"}'
14
+
15
+#curl -X POST "https://europe-west1-shuffle-241517.cloudfunctions.net/webhook_982995716e67c3a549092d3a3a7921cd" -H "Content-Type:application/json" -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" --data '{"name":"Keyboard Cat"}' -v
16
+
17
+## GET HOOK
18
+#curl http://localhost:5001/api/v1/hooks/b4ba07c9-45d4-41f2-b260-83c8e99eba0c -H "Authorization: Bearer "
19
+
20
+#curl https://shuffler.io/api/v1/hooks/b4ba07c9-45d4-41f2-b260-83c8e99eba0c -H "Authorization: Bearer "
21
+
22
+
23
+#curl -X POST "https://europe-west1-shuffle-241517.cloudfunctions.net/webhook_3ceff795-ce9a-43a2-a2f5-d4401a6e772d" -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" --data 'wut' 
24
+#curl POST "https://europe-west1-shuffler.cloudfunctions.net/outlooktrigger_be4dbb0a-d396-4544-bc36-e57d1bdb2e40" -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" --data 'wut' -vvv
25
+
26
+curl -X POST "http://localhost:5002/api/v1/hooks/webhook_f22b5e54-e55d-48e5-a1d1-f40453513fd3" -H "Content-Type:application/json" --data '{"test": {"hello": "HEYOOOO"}}'

+ 1 - 0
Shuffle/backend/tests/hotload.sh

1
+curl http://localhost:5001/api/v1/apps/run_hotload -H "Authorization: Bearer e08c6f22-9a55-4557-b008-04388cc51fb0"

+ 1 - 0
Shuffle/backend/tests/list_files.sh

1
+curl -XGET http://192.168.3.6:5001/api/v1/files -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4"

+ 2 - 0
Shuffle/backend/tests/migrate_db.sh

1
+curl -XPOST -v localhost:5001/api/v1/migrate_database -H 'Authorization: Bearer 0184d7be-33c1-4391-bf9c-dfb8508a4ea2'
2
+

+ 69 - 0
Shuffle/backend/tests/run_function.py

1
+# This is a script to test a function by itself
2
+
3
+import requests
4
+import json
5
+
6
+def invoke(url, headers, message): 
7
+    # Used for testing
8
+    try:
9
+        ret = requests.post(url, headers=headers, json=message, timeout=5)
10
+        print(ret.text)
11
+        print(ret.status_code)
12
+    except requests.exceptions.ConnectionError as e:
13
+        print(f"Requesterror: {e}")
14
+
15
+def invoke_multi(url, headers, message):
16
+    cnt = 0
17
+    maxcnt = 100
18
+    print("Running %d requests towards %s." % (maxcnt, url))
19
+    while(1):
20
+        try:
21
+            ret = requests.post(url, headers=headers, json=message, timeout=1)
22
+            print(ret.status_code)
23
+        except requests.exceptions.ConnectionError as e:
24
+            print(f"Connectionerror: {e}")
25
+        except requests.exceptions.ReadTimeout as e:
26
+            print(f"Readtimeout: {e}")
27
+
28
+        cnt += 1
29
+        if cnt == maxcnt:
30
+            break
31
+
32
+    print("Done :)")
33
+
34
+if __name__ == "__main__":
35
+    # Specific thingies for hello_world
36
+    message = {
37
+        "parameters": [{
38
+            "id_": "asd",
39
+            "name": "call",
40
+            "value": "REPEAT THIS DATA PLEASE THANKS",
41
+            "variant": "STATIC_VALUE",
42
+        }],
43
+        "name": "repeat_back_to_me",
44
+        "execution_id": "asd",
45
+        "label": "",
46
+        "position": "",
47
+        "app_name": "hello_world",
48
+        "app_version": "1.0.0",
49
+        "label": "lul",
50
+        "priority": "1",
51
+        "id_": "test",
52
+        "id": "test",
53
+        "authorization": "hey",
54
+    }
55
+
56
+    apikey = ""
57
+    headers = {
58
+        "Content-Type": "application/json",
59
+        "Authorization": f"Bearer {apikey}" 
60
+    }
61
+
62
+    location = "europe-west2"
63
+    functionname = "hello-world-1-0-6"
64
+    project = "shuffler"
65
+
66
+    url = f"https://{location}-{project}.cloudfunctions.net/{functionname}"
67
+    print(url)
68
+    invoke(url, headers, message)
69
+    #invoke_multi(url, headers, message)

+ 2 - 0
Shuffle/backend/tests/scheduleapps.sh

1
+
2
+curl localhost:5000/api/v1/schedules/apps 

+ 2 - 0
Shuffle/backend/tests/schedules.sh

1
+# Fails cus of unmarshal
2
+curl -XPOST http://localhost:5001/api/v1/workflows/1d9d8ce2-566e-4c3f-8a37-5d6c7d2000b5/schedule -d '{"name": "hey", "frequency": "*/1 * * * *", "execution_argument": "{\"test\": \"hey\"}"}' -H "Authorization: Bearer WUT"

+ 3 - 0
Shuffle/backend/tests/sendmail.sh

1
+#curl -X POST -H "Content-Type: application/json" shuffler.io/functions/sendmail -H "Authorization: Bearer " -d '{"target": "frikky@shuffler.io", "body": "Hey, this is a body for something to look at", "subject": "SOS check me", "type": "alert", "sender_company": "shuffler"}'
2
+
3
+curl -X POST -H "Content-Type: application/json" localhost:5001/functions/sendmail -H "Authorization: Bearer " -d '{"targets": ["frikky@shuffler.io", "rheyix.yt@gmail.com"], "body": "Hey, this is a body for something to look at", "subject": "SOS check me", "type": "alert", "sender_company": "shuffler"}'

+ 1 - 0
Shuffle/backend/tests/stop_execution.sh

1
+curl "http://localhost:5001/api/v1/environments/Shuffle/stop" -H "Authorization: Bearer e663cf93-7f10-4560-bef0-303f14aad982"

+ 7 - 0
Shuffle/backend/tests/testWorkflows.sh

1
+#!/bin/bash
2
+# Should give 401
3
+curl localhost:5000/api/v1/uploadResult/asdasd -d {}
4
+echo
5
+
6
+# Should give 200 if it exists
7
+curl localhost:5000/api/v1/uploadResult/e07910a06a086c83ba41827aa00b26ed -d '{"title": "helo","description": "wut", "type": "hi", "source": "wutface", "sourceRef": "halvor hei"}'

+ 210 - 0
Shuffle/backend/tests/test_wrappers.py

1
+import re
2
+import json
3
+
4
+def parse_nested_param(string, level):
5
+    """
6
+    Generate strings contained in nested (), indexing i = level
7
+    """
8
+    if len(re.findall("\(", string)) == len(re.findall("\)", string)):
9
+        LeftRightIndex = [x for x in zip(
10
+        [Left.start()+1 for Left in re.finditer('\(', string)], 
11
+        reversed([Right.start() for Right in re.finditer('\)', string)]))]
12
+
13
+    elif len(re.findall("\(", string)) > len(re.findall("\)", string)):
14
+        return parse_nested_param(string + ')', level)
15
+    elif len(re.findall("\(", string)) < len(re.findall("\)", string)):
16
+        return parse_nested_param('(' + string, level)
17
+
18
+    else:
19
+        return 'Failed to parse params'
20
+
21
+    try:
22
+        return [string[LeftRightIndex[level][0]:LeftRightIndex[level][1]]]
23
+    except IndexError:
24
+        return [string[LeftRightIndex[level+1][0]:LeftRightIndex[level+1][1]]]
25
+
26
+# Parses the deepest part
27
+def maxDepth(S): 
28
+    current_max = 0
29
+    max = 0
30
+    n = len(S) 
31
+  
32
+    # Traverse the input string 
33
+    for i in range(n): 
34
+        if S[i] == '(': 
35
+            current_max += 1
36
+  
37
+            if current_max > max: 
38
+                max = current_max 
39
+        elif S[i] == ')': 
40
+            if current_max > 0: 
41
+                current_max -= 1
42
+            else: 
43
+                return -1
44
+  
45
+    # finally check for unbalanced string 
46
+    if current_max != 0: 
47
+        return -1
48
+  
49
+    return max-1
50
+
51
+def parse_type(data, thistype): 
52
+    if data == None:
53
+        return "Empty"
54
+
55
+    if "int" in thistype:
56
+        try:
57
+            return int(data)
58
+        except ValueError:
59
+            print("ValueError while casting %s" % data)
60
+            return data
61
+    if "lower" in thistype:
62
+        return data.lower()
63
+    if "upper" in thistype:
64
+        return data.upper()
65
+    if "trim" in thistype:
66
+        return data.strip()
67
+    if "strip" in thistype:
68
+        return data.strip()
69
+    if "split" in thistype:
70
+        # Should be able to split anything
71
+        return data.split()
72
+    if "len" in thistype or "length" in thistype:
73
+        return len(data)
74
+    if "parse" in thistype:
75
+        splitvalues = []
76
+        default_error = """Error. Expected syntax: parse(["hello","test1"],0:1)""" 
77
+        if "," in data:
78
+            splitvalues = data.split(",")
79
+
80
+            for item in range(len(splitvalues)):
81
+                splitvalues[item] = splitvalues[item].strip()
82
+        else:
83
+            return default_error 
84
+
85
+        lastsplit = []
86
+        if ":" in splitvalues[-1]:
87
+            lastsplit = splitvalues[-1].split(":")
88
+        else:
89
+            try:
90
+                lastsplit = [int(splitvalues[-1])]
91
+            except ValueError:
92
+                return default_error
93
+
94
+        try:
95
+            parsedlist = ",".join(splitvalues[0:-1])
96
+            print(parsedlist)
97
+            print(lastsplit)
98
+
99
+            if len(lastsplit) > 1:
100
+                tmp = json.loads(parsedlist)[int(lastsplit[0]):int(lastsplit[1])]
101
+            else:
102
+                tmp = json.loads(parsedlist)[lastsplit[0]]
103
+
104
+            print(tmp)
105
+            return tmp
106
+        except IndexError as e:
107
+            return default_error
108
+
109
+# Parses the INNER value and recurses until everything is done
110
+def parse_wrapper(data):
111
+    try:
112
+        if "(" not in data or ")" not in data:
113
+            return data
114
+    except TypeError:
115
+        return data
116
+
117
+    print("Running %s" % data)
118
+
119
+    # Look for the INNER wrapper first, then move out
120
+    wrappers = ["int", "number", "lower", "upper", "trim", "strip", "split", "parse", "len", "length"]
121
+    found = False
122
+    for wrapper in wrappers:
123
+        if wrapper not in data.lower():
124
+            continue
125
+
126
+        found = True
127
+        break
128
+
129
+    if not found:
130
+        return data
131
+
132
+    # Do stuff here.
133
+    innervalue = parse_nested_param(data, maxDepth(data)-0)
134
+    outervalue = parse_nested_param(data, maxDepth(data)-1)
135
+    print("INNER: ", outervalue)
136
+    print("OUTER: ", outervalue)
137
+
138
+    if outervalue != innervalue:
139
+        #print("Outer: ", outervalue, " inner: ", innervalue)
140
+        for key in range(len(innervalue)):
141
+            # Replace OUTERVALUE[key] with INNERVALUE[key] in data.
142
+            print("Replace %s with %s in %s" % (outervalue[key], innervalue[key], data))
143
+            data = data.replace(outervalue[key], innervalue[key])
144
+    else:
145
+        for thistype in wrappers:
146
+            if thistype not in data.lower():
147
+                continue
148
+
149
+            parsed_value = parse_type(innervalue[0], thistype)
150
+            return parsed_value
151
+
152
+    print("DATA: %s\n" % data)
153
+    return parse_wrapper(data)
154
+
155
+def parse_wrapper_start(data):
156
+    newdata = []
157
+    newstring = ""
158
+    record = True
159
+    paranCnt = 0
160
+    for char in data:
161
+        if char == "(":
162
+            paranCnt += 1
163
+
164
+            if not record:
165
+                record = True 
166
+
167
+        if record:
168
+            newstring += char
169
+
170
+        if paranCnt == 0 and char == " ":
171
+            newdata.append(newstring)
172
+            newstring = ""
173
+            record = True
174
+
175
+        if char == ")":
176
+            paranCnt -= 1
177
+
178
+            if paranCnt == 0:
179
+                record = False
180
+
181
+    if len(newstring) > 0:
182
+        newdata.append(newstring)
183
+
184
+    parsedlist = []
185
+    non_string = False
186
+    for item in newdata:
187
+        ret = parse_wrapper(item)
188
+        if not isinstance(ret, str):
189
+            non_string = True
190
+
191
+        parsedlist.append(ret)
192
+
193
+    if len(parsedlist) > 0 and not non_string:
194
+        return " ".join(parsedlist)
195
+    elif len(parsedlist) == 1 and non_string:
196
+        return parsedlist[0]
197
+    else:
198
+        print("Casting back to string because multi: ", parsedlist)
199
+        newlist = []
200
+        for item in parsedlist:
201
+            try:
202
+                newlist.append(str(item))
203
+            except ValueError:
204
+                newlist.append("parsing_error")
205
+        return " ".join(newlist)
206
+
207
+data = "split(hello there)"
208
+data = """parse(["testing", "what", "is this"], 0:2)"""
209
+#data = "int(int(2))"
210
+print("RET: ", parse_wrapper_start(data))

+ 3 - 0
Shuffle/backend/tests/triggers.sh

1
+# curl -H "Content-Type: application/json" localhost:5001/api/v1/triggers/9e845679-5843-4959-a76c-a6d664e9df35 -H "Authorization: Bearer 377469e8-dd5d-4521-8d9e-416d8d2f6fd4"
2
+
3
+curl -XPOST http://localhost:5001/api/v1/hooks/webhook_1b968d49-2d78-4132-bf91-0f3a1f6a79f3 -d '{}'

+ 4 - 0
Shuffle/backend/tests/upload.sh

1
+# hello
2
+this is line 2
3
+and 3
4
+Is it a python problem?

+ 13 - 0
Shuffle/backend/tests/users.sh

1
+curl http://localhost:5001/api/v1/users/register -H "Authorization: Bearer 36a3eb38-0070-41c6-b20a-2a3e0941d10e" -d '{"username": "username1", "password": ""}'
2
+
3
+echo
4
+curl http://localhost:5001/api/v1/users -H "Authorization: Bearer 36a3eb38-0070-41c6-b20a-2a3e0941d10e" 
5
+
6
+echo UPDATE
7
+curl -XPUT http://localhost:5001/api/v1/users/updateuser -H "Authorization: Bearer 36a3eb38-0070-41c6-b20a-2a3e0941d10e" -d '{"user_id": "id", "role": "admin"}'
8
+
9
+echo 
10
+curl -XDELETE http://localhost:5001/api/v1/users/userid -H "Authorization: Bearer 36a3eb38-0070-41c6-b20a-2a3e0941d10e" 
11
+
12
+echo
13
+curl -XPOST http://localhost:5001/api/v1/users/generateapikey -H "Authorization: Bearer 36a3eb38-0070-41c6-b20a-2a3e0941d10e" -d '{"user_id": "390efa79-73a3-454b-8b1d-38f56eec14ad"}'

+ 14 - 0
Shuffle/backend/tests/validate_app_values.sh

1
+# ExecutionOrg MUST be executing.
2
+curl -XPOST http://localhost:5001/api/v1/orgs/b199646b-16d2-456d-9fd6-b9972e929466/validate_app_values -d '{
3
+	"append": true,
4
+	"workflow_check": true,
5
+	"authorization": "1aae630c-ccaf-4cb5-87f9-8a9e0a9afd11",
6
+	"execution_ref": "c59ff288-4f02-4d02-b839-133d55c7fdf0",
7
+	"org_id": "b199646b-16d2-456d-9fd6-b9972e929466",
8
+	"values": [{
9
+		"app": "testing",
10
+		"action": "repeat_back_to_me",
11
+		"parameternames": ["call"],
12
+		"parametervalues": ["hey", "ho", "lets", "go"]
13
+	}]
14
+}'

+ 4 - 0
Shuffle/backend/tests/websocket.sh

1
+#!/bin/bash
2
+curl http://localhost:5001/ws -H "Connections: Upgrade"
3
+
4
+#curl -X POST "https://europe-west1-shuffle-241517.cloudfunctions.net/webhook_982995716e67c3a549092d3a3a7921cd" -H "Content-Type:application/json" -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" --data '{"name":"Keyboard Cat"}' -v

+ 202 - 0
Shuffle/backend/tests/workflowdata.json

1
+[
2
+	{
3
+	"actions": [
4
+      {
5
+        "app_name": "hello_world",
6
+        "app_version": "1.0.0",
7
+        "errors": [],
8
+        "id_": "2686a5d4-531d-158f-6b1a-0c1d23481304",
9
+        "is_valid": true,
10
+        "label": "check_bool",
11
+        "name": "check_bool",
12
+		"environment": "cloud",
13
+        "parameters": [],
14
+        "position": {
15
+          "x": 329.98133726556375,
16
+          "y": 160.01013778166904
17
+        },
18
+        "priority": 3
19
+      }
20
+    ],
21
+    "branches": [
22
+      {
23
+        "destination_id": "6478ecae-b10e-88e9-e34d-9bbe6aff393d",
24
+        "id_": "5fd6a357-ae33-b1af-5dc2-0306efa28887",
25
+        "source_id": "2686a5d4-531d-158f-6b1a-0c1d23481304"
26
+      },
27
+      {
28
+        "destination_id": "2686a5d4-531d-158f-6b1a-0c1d23481304",
29
+        "id_": "d46d0b05-5757-5084-c339-70ac63985781",
30
+        "source_id": "6478ecae-b10e-88e9-e34d-9bbe6aff393d"
31
+      }
32
+    ],
33
+    "conditions": [
34
+      {
35
+        "app_name": "Builtin",
36
+        "app_version": "1.0.0",
37
+        "conditional": "",
38
+        "errors": [],
39
+        "id_": "6478ecae-b10e-88e9-e34d-9bbe6aff393d",
40
+        "is_valid": true,
41
+        "label": "Condition",
42
+        "name": "Condition",
43
+        "position": {
44
+          "x": 320.97142988802364,
45
+          "y": 394.9753467582139
46
+        }
47
+      }
48
+    ],
49
+    "description": "",
50
+    "errors": [],
51
+    "id_": "a5f82cfd-0f38-3474-20e2-f757f3718707",
52
+    "is_valid": true,
53
+    "name": "asd2",
54
+    "start": "2686a5d4-531d-158f-6b1a-0c1d23481304",
55
+    "tags": [],
56
+    "transforms": [],
57
+    "triggers": [],
58
+    "workflow_variables": []
59
+  },
60
+  {
61
+    "actions": [
62
+      {
63
+        "app_name": "hello_world",
64
+        "app_version": "1.0.0",
65
+        "errors": [],
66
+        "id_": "51df7c4f-b856-1aca-402b-9fec660b6505",
67
+        "is_valid": true,
68
+        "label": "wut",
69
+        "name": "hello_world",
70
+        "parameters": [],
71
+        "position": {
72
+          "x": 250,
73
+          "y": 150
74
+        },
75
+        "priority": 3
76
+      },
77
+      {
78
+        "app_name": "hello_world",
79
+        "app_version": "1.0.0",
80
+        "errors": [],
81
+        "id_": "36975212-3b9a-2e4c-4ad7-0ee5a6325842",
82
+        "is_valid": true,
83
+        "label": "check_bool",
84
+        "name": "check_bool",
85
+        "parameters": [],
86
+        "position": {
87
+          "x": 241.00207363715955,
88
+          "y": 294.9896318142021
89
+        },
90
+        "priority": 3
91
+      }
92
+    ],
93
+    "branches": [
94
+      {
95
+        "destination_id": "36975212-3b9a-2e4c-4ad7-0ee5a6325842",
96
+        "id_": "44231b8f-4331-764e-0c1d-ccbc901d1309",
97
+        "source_id": "51df7c4f-b856-1aca-402b-9fec660b6505"
98
+      },
99
+      {
100
+        "destination_id": "51df7c4f-b856-1aca-402b-9fec660b6505",
101
+        "id_": "9b3dc561-e879-4877-c32e-ab7149d83b39",
102
+        "source_id": "36975212-3b9a-2e4c-4ad7-0ee5a6325842"
103
+      }
104
+    ],
105
+    "conditions": [],
106
+    "description": "",
107
+    "errors": [],
108
+    "id_": "4e437698-fc18-29d3-e875-969b57354685",
109
+    "is_valid": true,
110
+    "name": "hi",
111
+    "start": "51df7c4f-b856-1aca-402b-9fec660b6505",
112
+    "tags": [],
113
+    "transforms": [],
114
+    "triggers": [],
115
+    "workflow_variables": []
116
+  },
117
+  {
118
+    "actions": [
119
+      {
120
+        "app_name": "hello_world",
121
+        "app_version": "1.0.0",
122
+        "errors": [],
123
+        "id_": "d0dbd1c4-dd61-6d4a-70e1-ac05dad0fe1f",
124
+        "is_valid": true,
125
+        "label": "i am bool",
126
+        "name": "check_bool",
127
+        "parameters": [],
128
+        "position": {
129
+          "x": 377.9998262128892,
130
+          "y": 330.01190441708906
131
+        },
132
+        "priority": 3
133
+      },
134
+      {
135
+        "app_name": "hello_world",
136
+        "app_version": "1.0.0",
137
+        "errors": [],
138
+        "id_": "39353c1c-b179-152c-f977-615a15a5de37",
139
+        "is_valid": true,
140
+        "label": "hello_world",
141
+        "name": "hello_world",
142
+        "parameters": [],
143
+        "position": {
144
+          "x": 375.2590614780782,
145
+          "y": 160.6226941577116
146
+        },
147
+        "priority": 3
148
+      }
149
+    ],
150
+    "branches": [
151
+      {
152
+        "destination_id": "d0dbd1c4-dd61-6d4a-70e1-ac05dad0fe1f",
153
+        "id_": "7c81993f-5fd6-fc83-2f41-8074d6c8dc25",
154
+        "source_id": "39353c1c-b179-152c-f977-615a15a5de37"
155
+      }
156
+    ],
157
+    "conditions": [],
158
+    "description": "",
159
+    "errors": [],
160
+    "id_": "af8467be-43ca-d38b-3f7e-9aeb922fb21a",
161
+    "is_valid": true,
162
+    "name": "new!",
163
+    "start": "39353c1c-b179-152c-f977-615a15a5de37",
164
+    "tags": [],
165
+    "transforms": [],
166
+    "triggers": [],
167
+    "workflow_variables": []
168
+  },
169
+  {
170
+    "actions": [
171
+      {
172
+        "app_name": "Builtin",
173
+        "app_version": "1.0.0",
174
+        "errors": [],
175
+        "id_": "7dddec9a-b493-8b58-9234-1b74dd9b420a",
176
+        "is_valid": true,
177
+        "label": "Boolean",
178
+        "name": "Boolean",
179
+        "parameters": [],
180
+        "position": {
181
+          "x": 350,
182
+          "y": 250
183
+        },
184
+        "priority": 3
185
+      }
186
+    ],
187
+    "branches": [],
188
+    "conditions": [],
189
+    "description": "",
190
+    "errors": [
191
+      "Action Builtin.Boolean does not exist"
192
+    ],
193
+    "id_": "07bae551-21e6-c0f5-4db3-81e8a1e8a805",
194
+    "is_valid": false,
195
+    "name": "wutface",
196
+    "start": "7dddec9a-b493-8b58-9234-1b74dd9b420a",
197
+    "tags": [],
198
+    "transforms": [],
199
+    "triggers": [],
200
+    "workflow_variables": []
201
+  }
202
+]

+ 23 - 0
Shuffle/backend/tests/workflowresults.sh

1
+#curl -X POST -H "Content-Type: application/json" localhost:5001/api/v1/workflows/3d14ca4a-67bd-8dfb-2673-2864f1ccf59c/execute -d '{"workflow_id": "3d14ca4a-67bd-8dfb-2673-2864f1ccf59c", "execution_id": "eaaa8d19-a761-12b8-cac2-f34eb50c3711"}'
2
+
3
+curl -X POST -H "Content-Type: application/json" https://shuffle-241517.appspot.com/api/v1/workflows/3d14ca4a-67bd-8dfb-2673-2864f1ccf59c/execute -d '{"workflow_id": "3d14ca4a-67bd-8dfb-2673-2864f1ccf59c", "execution_id": "eaaa8d19-a761-12b8-cac2-f34eb50c3711"}'
4
+
5
+#curl -X POST http://localhost:5001/api/v1/workflows/streams -H "Content-Type: application/json" \
6
+#	-d '{"execution_id": "eaaa8d19-a761-12b8-cac2-f34eb50c3711",
7
+#        "result": "hello_result",
8
+#        "started_at": 1562309342,
9
+#		"authorization": "afcc298d-c6c2-4b0d-8221-1603b44d072d",
10
+#        "status": "ABORTED",
11
+#		"action": {
12
+#			"app_name": "hi",
13
+#			"app_version": "ho",
14
+#			"id_": "this_is_an_id",
15
+#			"label": "wut",
16
+#			"name": "stream_testing",
17
+#			"parameters": [],
18
+#			"position": {
19
+#				"x": 100,
20
+#				"y": 100
21
+#			},
22
+#			"priority": 1
23
+#		}}'

Plik diff jest za duży
+ 15 - 0
Shuffle/backend/tests/workflows.sh


+ 170 - 0
Shuffle/docker-compose.yml

1
+services:
2
+  frontend:
3
+    image: ghcr.io/shuffle/shuffle-frontend:latest
4
+    container_name: shuffle-frontend
5
+    hostname: shuffle-frontend
6
+    ports:
7
+      - "${FRONTEND_PORT}:80"
8
+      - "${FRONTEND_PORT_HTTPS}:443"
9
+    networks:
10
+      - shuffle
11
+    environment:
12
+      - BACKEND_HOSTNAME=${BACKEND_HOSTNAME}
13
+    restart: unless-stopped
14
+    depends_on:
15
+      - backend
16
+  backend:
17
+    image: ghcr.io/shuffle/shuffle-backend:latest
18
+    container_name: shuffle-backend
19
+    hostname: ${BACKEND_HOSTNAME}
20
+    # Here for debugging:
21
+    ports:
22
+      - "${BACKEND_PORT}:5001"
23
+    networks:
24
+      - shuffle
25
+    volumes:
26
+      - /var/run/docker.sock:/var/run/docker.sock
27
+      - ${SHUFFLE_APP_HOTLOAD_LOCATION}:/shuffle-apps:z
28
+      - ${SHUFFLE_FILE_LOCATION}:/shuffle-files:z
29
+    env_file: .env
30
+    environment:
31
+      #- DOCKER_HOST=tcp://docker-socket-proxy:2375
32
+      - SHUFFLE_APP_HOTLOAD_FOLDER=/shuffle-apps
33
+      - SHUFFLE_FILE_LOCATION=/shuffle-files
34
+    restart: unless-stopped
35
+  orborus:
36
+    image: ghcr.io/shuffle/shuffle-orborus:latest
37
+    container_name: shuffle-orborus
38
+    hostname: shuffle-orborus
39
+    networks:
40
+      - shuffle
41
+    volumes:
42
+      - /var/run/docker.sock:/var/run/docker.sock
43
+    environment:
44
+      - SHUFFLE_APP_SDK_TIMEOUT=300
45
+      - ENVIRONMENT_NAME=Shuffle
46
+      - ORG_ID=Shuffle
47
+      - BASE_URL=http://${OUTER_HOSTNAME}:5001
48
+      - DOCKER_API_VERSION=1.40
49
+      - HTTP_PROXY=${HTTP_PROXY}
50
+      - HTTPS_PROXY=${HTTPS_PROXY}
51
+      - SHUFFLE_PASS_WORKER_PROXY=${SHUFFLE_PASS_WORKER_PROXY}
52
+      - SHUFFLE_PASS_APP_PROXY=${SHUFFLE_PASS_APP_PROXY}
53
+      # - SHUFFLE_APP_REPLICAS=${SHUFFLE_APP_REPLICAS}
54
+      - SHUFFLE_STATS_DISABLED=true
55
+      - SHUFFLE_LOGS_DISABLED=true
56
+      - SHUFFLE_SWARM_CONFIG=run
57
+      - CLEANUP=false
58
+      - SHUFFLE_WORKER_IMAGE=ghcr.io/shuffle/shuffle-worker:latest
59
+    env_file: .env
60
+    restart: unless-stopped
61
+    security_opt:
62
+      - seccomp:unconfined
63
+  opensearch:
64
+    image: opensearchproject/opensearch:3.2.0
65
+    hostname: shuffle-opensearch
66
+    container_name: shuffle-opensearch
67
+    environment:
68
+      - "OPENSEARCH_JAVA_OPTS=-Xms3072m -Xmx3072m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM
69
+      - bootstrap.memory_lock=true
70
+      - DISABLE_PERFORMANCE_ANALYZER_AGENT_CLI=true
71
+      - cluster.initial_master_nodes=shuffle-opensearch
72
+      - cluster.routing.allocation.disk.threshold_enabled=false
73
+      - cluster.name=shuffle-cluster
74
+      - node.name=shuffle-opensearch
75
+      - node.store.allow_mmap=false
76
+      - discovery.seed_hosts=shuffle-opensearch
77
+      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=${SHUFFLE_OPENSEARCH_PASSWORD}
78
+    ulimits:
79
+      memlock:
80
+        soft: -1
81
+        hard: -1
82
+      nofile:
83
+        soft: 65536
84
+        hard: 65536
85
+    volumes:
86
+      - shuffle-database:/usr/share/opensearch/data:z
87
+    ports:
88
+      - "${SHUFFLE_OPENSEARCH_PORT:-9200}:9200"
89
+    networks:
90
+      - shuffle
91
+    restart: unless-stopped
92
+
93
+  # OPTIONAL: advanced monitoring with cAdvisor
94
+  # If you have a seperate orborus config YAML on another server,
95
+  # Please add it there as well if you want to monitor that server too.
96
+  # FYI: both servers will show up on different dashboards.
97
+  # cadvisor:
98
+  #   image: gcr.io/cadvisor/cadvisor:latest
99
+  #   volumes:
100
+  #     - /:/rootfs:ro
101
+  #     - /var/run:/var/run:ro
102
+  #     - /sys:/sys:ro
103
+  #     - /var/lib/docker/:/var/lib/docker:ro
104
+  #     - /dev/disk/:/dev/disk:ro
105
+  #     - /var/run/docker.sock:/var/run/docker.sock:ro
106
+  #   ports:
107
+  #     - "8080:8080"
108
+  #   privileged: true
109
+  #   devices:
110
+  #     - /dev/kmsg:/dev/kmsg
111
+  #   networks:
112
+  #     - shuffle
113
+
114
+  #memcached:
115
+  #  image: memcached:latest
116
+  #  container_name: shuffle-cache
117
+  #  hostname: shuffle-cache
118
+  #  command: memcached -m 2048 -c 2500
119
+  #  environment:
120
+  #    - MEMCACHED_MEMORY=2048
121
+  #    - MEMCACHED_MAX_CONNECTIONS=2500
122
+  #  ports:
123
+  #    - 11211:11211
124
+  #  networks:
125
+  #  -  shuffle
126
+  #  -  swarm_executions
127
+
128
+  #docker-socket-proxy:
129
+  #  image: tecnativa/docker-socket-proxy
130
+  #  container_name: docker-socket-proxy
131
+  #  hostname: docker-socket-proxy
132
+  #  privileged: true
133
+  #  environment:
134
+  #    - SERVICES=1
135
+  #    - TASKS=1
136
+  #    - NETWORKS=1
137
+  #    - NODES=1
138
+  #    - BUILD=1
139
+  #    - IMAGES=1
140
+  #    - GRPC=1
141
+  #    - CONTAINERS=1
142
+  #    - PLUGINS=1
143
+  #    - SYSTEM=1
144
+  #    - VOLUMES=1
145
+  #    - INFO=1
146
+  #    - DISTRIBUTION=1
147
+  #    - POST=1
148
+  #    - AUTH=1
149
+  #    - SECRETS=1
150
+  #    - SWARM=1
151
+  #  volumes:
152
+  #    - /var/run/docker.sock:/var/run/docker.sock
153
+  #  networks:
154
+  #    - shuffle
155
+  #
156
+
157
+volumes:
158
+  shuffle-database:
159
+    driver: local
160
+    driver_opts:
161
+      type: none
162
+      device: ${DB_LOCATION}
163
+      o: bind
164
+
165
+networks:
166
+  shuffle:
167
+    driver: bridge
168
+    # driver_opts:
169
+    #   com.docker.network.driver.mtu: 1460
170
+    # uncomment to set MTU for swarm mode. MTU should be whatever is your host's preferred MTU is: https://shuffler.io/docs/troubleshooting#TLS_timeout_error/Timeout_Errors/EOF_Errors

+ 63 - 0
Shuffle/frontend/Dockerfile

1
+# Build environment
2
+FROM node:23 AS builder
3
+
4
+ENV NODE_OPTIONS="--max-old-space-size=4096"
5
+
6
+RUN mkdir /usr/src/app
7
+WORKDIR /usr/src/app
8
+ENV PATH="/usr/src/app/node_modules/.bin:$PATH"
9
+
10
+COPY package.json /usr/src/app/package.json
11
+COPY package-lock.json /usr/src/app/package-lock.json
12
+
13
+RUN npm config set fetch-retries 3   # Number of retry attempts (default is 2)
14
+RUN npm config set fetch-retry-mintimeout 5000  # Minimum wait time before retrying (in ms, default is 10000)
15
+RUN npm config set fetch-retry-maxtimeout 60000 # Maximum wait time before retrying (in ms, default is 60000)
16
+RUN npm config set fetch-timeout 60000 # Overall fetch timeout (in ms, default is 300000)
17
+RUN npm install --legacy-peer-deps
18
+
19
+# copy only required files to not trigger rebuilding every time
20
+COPY ./certs /usr/src/app/certs/
21
+COPY ./public /usr/src/app/public/
22
+COPY ./src /usr/src/app/src/
23
+COPY ./*.sh /usr/src/app/
24
+COPY ./*.json /usr/src/app/
25
+COPY ./index.html /usr/src/app/index.html
26
+COPY ./vite.config.js /usr/src/app/vite.config.js
27
+
28
+
29
+RUN npm run build
30
+
31
+# Production environment
32
+FROM nginx:1.29.3
33
+
34
+RUN mkdir -p /usr/share/nginx/html/build
35
+RUN mkdir -p /usr/share/nginx/html/css
36
+RUN mkdir -p /usr/share/nginx/html/js
37
+RUN mkdir -p /usr/share/nginx/html/img
38
+
39
+
40
+# Localhost certificate challenge: Y#XwrJ#DoZGz2w6x
41
+# Cert challenge doesn't matter to be here or not, as ALL production setups should be using their own certificates + reverse proxy: https://shuffler.io/docs/configuration#using-the-nginx-reverse-proxy-for-tls/ssl
42
+COPY --from=builder /usr/src/app/build /usr/share/nginx/html
43
+COPY --from=builder /usr/src/app/certs/fullchain.pem /etc/nginx/fullchain.cert.pem
44
+COPY --from=builder /usr/src/app/certs/privkey.pem /etc/nginx/privkey.pem
45
+
46
+# install CONFD
47
+RUN apt-get update && apt-get install -y curl && apt-get clean
48
+COPY ./confd/templates/nginx.conf /etc/nginx/nginx.conf.tmpl
49
+
50
+## OLD CONFD THINGS (not compatible with arm)
51
+#ENV CONFD_VERSION 0.16.0
52
+#RUN curl -sSL https://github.com/kelseyhightower/confd/releases/download/v${CONFD_VERSION}/confd-${CONFD_VERSION}-linux-amd64 -o /usr/local/bin/confd && \
53
+#    chmod +x /usr/local/bin/confd
54
+#COPY ./confd /etc/confd
55
+# rewrite command & entrypoint with ours
56
+
57
+COPY ./entrypoint.sh /
58
+ENV BACKEND_HOSTNAME="shuffle-backend"
59
+ENTRYPOINT [ "/entrypoint.sh" ]
60
+CMD ["nginx", "-g", "daemon off;"]
61
+
62
+EXPOSE 80
63
+EXPOSE 443

+ 23 - 0
Shuffle/frontend/README.md

1
+## Lalits frontend magic
2
+
3
+## Localhost Certificate info:
4
+
5
+
6
+Creating a localhost certificate:
7
+
8
+```
9
+openssl genrsa -out privkey.pem 2048
10
+openssl req -new -key privkey.pem -out certreq.csr
11
+openssl x509 -req -days 3650 -in certreq.csr -signkey privkey.pem -out fullchain.pem
12
+```
13
+
14
+## Using your own certificate
15
+If you have your own .crt and .key file, you can do it like this:
16
+```
17
+openssl x509 -in mycert.crt -out fullchain.cert.pem -outform PEM
18
+```
19
+
20
+The KEY file has to be named privkey.pem 
21
+```
22
+mv cert.key privkey.pem
23
+```

+ 3 - 0
Shuffle/frontend/build.sh

1
+npm run build
2
+rm -rf ../backend/go-app/build
3
+cp -r build/ ../backend/go-app/build

+ 19 - 0
Shuffle/frontend/certs/certreq.csr

1
+-----BEGIN CERTIFICATE REQUEST-----
2
+MIIDBTCCAe0CAQAwgYYxCzAJBgNVBAYTAkpQMQ4wDAYDVQQIDAVUb2t5bzEOMAwG
3
+A1UEBwwFVG9reW8xEDAOBgNVBAoMB1NodWZmbGUxEDAOBgNVBAsMB1NodWZmbGUx
4
+EDAOBgNVBAMMB1NodWZmbGUxITAfBgkqhkiG9w0BCQEWEmZyaWtreUBzaHVmZmxl
5
+ci5pbzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANLYQupj6hNpfKzy
6
+km3qFkTvuPFed2s6HkBBqKwgpZ6C7wdDuUrMd9SXguw9yhtgxezg+ui1O735ZL4W
7
+6ZpT/Pf3Staj7SHgXAGDnPwcTeWgbd9rZO02UlD70Bhj6SBKYdS8FHRFJvbCe6V0
8
+tY+pJhaTUogwPr1jJTvuHIqRhXHmUl88jVkMIj27FPTnUDEEbr+E8Q6LDYUfSxpw
9
+beZqpv8FRGP88H+WGb/CnbFsvJMbNkHbM2Wj9b6yYiHuw8WRuTODX5YOVefGSYn/
10
+h3JgniT2AdQAsacdB3avdE/b4Mq1FUVqsYaUGSXZ+Iysdo9/7zhYmb0UtaFMSQDA
11
+zsOlIrMCAwEAAaA5MBYGCSqGSIb3DQEJAjEJDAdTaHVmZmxlMB8GCSqGSIb3DQEJ
12
+BzESDBBZI1h3ckojRG9aR3oydzZ4MA0GCSqGSIb3DQEBCwUAA4IBAQCinafabm9w
13
+EqhWrgi0lXQ+iqcklvigSegaEZx5g8lr4qYADPqRWRnzZcq8Dzifx6HUsDs6Dv6/
14
+7INF6cwGxvPFv2CBEvS/KkPtWxHaoATxRxwGw9Vh88OQy/cE6/Jiu8t94QtGMLRL
15
+V0bUnLfc3OKqNM5YvRf8Z1jbO+tzZ4ul3lApHF7UGq01BIcna0D4JblnwP62M4MW
16
+vY++wzYqqsHAOrUN94tJQaTP1nc4u03lme+4duKY7G5yxMYn1A0aDp0IFeDvela9
17
+nylRCM3V5FK1aubXuQnVYoFnnwaM2hTBVTKJbaCCWQhM0e3XYd8mCQYmgf+qlXBN
18
+OG4enppZCsAJ
19
+-----END CERTIFICATE REQUEST-----

+ 9 - 0
Shuffle/frontend/confd/conf.d/nginx.conf.toml

1
+[template]
2
+src   = "nginx.conf"
3
+dest  = "/etc/nginx/nginx.conf"
4
+uid = 0
5
+gid = 0
6
+mode  = "0644"
7
+keys = [
8
+  "/",
9
+]

+ 127 - 0
Shuffle/frontend/confd/templates/nginx.conf

1
+user nobody nogroup;
2
+worker_processes auto;          # auto-detect number of logical CPU cores
3
+
4
+events {
5
+	worker_connections 512;       # set the max number of simultaneous connections (per worker process)
6
+}
7
+
8
+http {
9
+	client_max_body_size 250M;
10
+
11
+	include mime.types;
12
+
13
+	# thanks stackoverflow http://stackoverflow.com/a/5132440/2406040
14
+	gzip  on;
15
+	gzip_http_version 1.1;
16
+	gzip_vary on;
17
+	gzip_comp_level 6;
18
+	gzip_proxied any;
19
+	gzip_types text/plain text/css application/json application/javascript application/x-javascript text/javascript text/xml application/xml application/rss+xml application/atom+xml application/rdf+xml;
20
+
21
+	# make sure gzip does not lose large gzipped js or css files
22
+	# see http://blog.leetsoft.com/2007/07/25/nginx-gzip-ssl.html
23
+	gzip_buffers 16 8k;
24
+
25
+	# Disable gzip for certain browsers.
26
+	gzip_disable "MSIE [1-6].(?!.*SV1)";
27
+
28
+	server {
29
+		listen         80;
30
+		server_name    "localhost";
31
+
32
+		#location /static/js/* {
33
+		#	# avoid clickjacking
34
+		#	add_header X-Frame-Options DENY;
35
+		#	add_header X-Content-Type-Options nosniff;
36
+		#	add_header ;
37
+		#	# block MIME sniffing
38
+
39
+		#	# security headers
40
+		#	add_header X-XSS-Protection "1; mode=block";
41
+		#	# add_header Content-Security-Policy "default-src 'self'";
42
+		#	add_header Referrer-Policy "no-referrer";
43
+		#	server_tokens off;
44
+
45
+		#	root /usr/share/nginx/html;
46
+		#	gzip_static on;
47
+		#	expires 1y;
48
+		#	add_header Cache-Control public;
49
+		#	add_header ETag "";
50
+		#	try_files $uri /index.html;
51
+		#}
52
+
53
+		location / {
54
+			# avoid clickjacking
55
+			add_header X-Frame-Options DENY;
56
+			# block MIME sniffing
57
+			add_header X-Content-Type-Options nosniff;
58
+
59
+			# security headers
60
+			add_header X-XSS-Protection "1; mode=block";
61
+			# add_header Content-Security-Policy "default-src 'self'";
62
+			add_header Referrer-Policy "no-referrer";
63
+			server_tokens off;
64
+
65
+			root /usr/share/nginx/html;
66
+			gzip_static on;
67
+			expires 1y;
68
+			add_header Cache-Control public;
69
+			add_header ETag "";
70
+			try_files $uri /index.html;
71
+		}
72
+
73
+		location ~ /api/v(1|2) {
74
+			proxy_pass http://${BACKEND_HOSTNAME}:5001;
75
+			proxy_buffering off;
76
+			proxy_http_version 1.1;
77
+
78
+			proxy_connect_timeout 900;
79
+			proxy_send_timeout 900;
80
+			proxy_read_timeout 900;
81
+			send_timeout 900;
82
+		}
83
+
84
+	}
85
+
86
+	server {
87
+		listen              443 ssl;
88
+		server_name         "localhost";
89
+		ssl_certificate     fullchain.cert.pem;
90
+		ssl_certificate_key privkey.pem;
91
+		ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
92
+		ssl_ciphers         HIGH:!aNULL:!MD5;
93
+
94
+		location / {
95
+			# avoid clickjacking
96
+			add_header X-Frame-Options DENY;
97
+			# block MIME sniffing
98
+			add_header X-Content-Type-Options nosniff;
99
+
100
+			# security headers
101
+			add_header X-XSS-Protection "1; mode=block";
102
+			# add_header Content-Security-Policy "default-src 'self'";
103
+			add_header Referrer-Policy "no-referrer";
104
+			server_tokens off;
105
+
106
+			root /usr/share/nginx/html;
107
+			gzip_static on;
108
+			expires 1y;
109
+			add_header Cache-Control public;
110
+			add_header ETag "";
111
+			try_files $uri /index.html;
112
+		}
113
+
114
+		# Get the hostname from environment here?
115
+		location ~ /api/v(1|2) {
116
+			proxy_pass http://${BACKEND_HOSTNAME}:5001;
117
+
118
+			proxy_buffering off;
119
+			proxy_http_version 1.1;
120
+
121
+			proxy_connect_timeout 900;
122
+			proxy_send_timeout 900;
123
+			proxy_read_timeout 900;
124
+			send_timeout 900;
125
+		}
126
+	}
127
+}

+ 19 - 0
Shuffle/frontend/enable_subpathing.sh

1
+#!/bin/bash
2
+
3
+# Sekerovic Dragan, oneStep2 GmbH - 2023-09-08 - quick and dirty
4
+# purpose of the script: make shuffle reverse proxyable/run shuffle on a subpath instead of root
5
+
6
+if $(ls frontend/src/App.jsx.orig.* 1>/dev/null 2>&1); then
7
+    echo "shuffle src already patched for subpathing!"
8
+    echo "Aborting ..."
9
+    exit 1
10
+fi
11
+
12
+[[ "$1" != "" ]] && SUBPATH=$1 || SUBPATH="/shuffle"
13
+
14
+cat $0.tpl | sed "s,SUBPATH,$SUBPATH," > $0.run
15
+chmod 700 $0.run
16
+./$0.run
17
+rm ./$0.run
18
+
19
+exit 0

+ 139 - 0
Shuffle/frontend/enable_subpathing.sh.tpl

1
+#!/bin/bash
2
+
3
+cd frontend
4
+
5
+cd src
6
+cp App.jsx App.jsx.orig.`date -I`
7
+sed 's,startsWith("/,startsWith("SUBPATH/,g' -i App.jsx
8
+sed 's,path="/,path="SUBPATH/,g' -i App.jsx
9
+sed 's,location = "/,location = "SUBPATH/,g' -i App.jsx
10
+sed 's,window.location.origin;,"SUBPATH";,' -i App.jsx
11
+
12
+cd views
13
+for d in $(ls *.jsx); do cp $d $d.orig.`date -I`; done
14
+sed 's,pathname = "/,pathname = "SUBPATH/,g' -i *.jsx
15
+sed 's,navigate("/,navigate("SUBPATH/,g' -i *.jsx
16
+sed 's,navigate(`/,navigate(`SUBPATH/,g' -i *.jsx
17
+sed 's,href="/,href="SUBPATH/,g' -i *.jsx
18
+sed 's,path = "/,path = "SUBPATH/,g' -i *.jsx
19
+sed 's,link={"/,link={"SUBPATH/,g' -i *.jsx
20
+sed 's,to="/,to="SUBPATH/,g' -i *.jsx
21
+sed 's,to={"/,to={"SUBPATH/,g' -i *.jsx
22
+sed 's,${window.location.origin},SUBPATH,g' -i *.jsx
23
+cd ..
24
+
25
+cd components
26
+for d in $(ls *.jsx); do cp $d $d.orig.`date -I`; done
27
+sed 's,to="/,to="SUBPATH/,g' -i *.jsx
28
+sed 's,${window.location.origin},SUBPATH,g' -i *.jsx
29
+cd ../..
30
+
31
+cd confd/templates
32
+cp nginx.conf nginx.conf.orig.`date -I`
33
+sed 's,location / {,rewrite ^SUBPATH(/api/v1.*)$ $1 last;\n\n\t\tlocation / {,' -i nginx.conf
34
+cd ../..
35
+
36
+docker build -t ghcr.io/shuffle/shuffle-frontend:latest .
37
+
38
+
39
+cat << EOF
40
+
41
+If you want to reverse proxy shuffle using subpath "SUBPATH" you need to configure
42
+your web server - in this case nginx - to do the reverse proxying by at least adding 
43
+
44
+  location SUBPATH {
45
+    proxy_pass http://snooss-proxy:3421/;
46
+    rewrite SUBPATH/(.*)$ /$1 break;
47
+
48
+    proxy_read_timeout 300s;
49
+    
50
+    # proxy header
51
+    proxy_set_header Host $host;
52
+    proxy_set_header X-Real-IP $remote_addr;
53
+    proxy_set_header X-Forwarded-Proto $scheme;
54
+    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
55
+
56
+    # substitute html content response
57
+    proxy_set_header Accept-Encoding "";        # no compression allowed or sub_filter won't work
58
+    sub_filter 'href="/' 'href="SUBPATH/';
59
+    sub_filter 'href:"/' 'href:"SUBPATH/';
60
+    sub_filter '/static' 'SUBPATH/static';
61
+    sub_filter '/images' 'SUBPATH/images';
62
+    sub_filter_types *;
63
+    sub_filter_once off;
64
+  }
65
+
66
+to your nginx configuration.
67
+EOF
68
+
69
+
70
+exit 0#!/bin/bash
71
+
72
+cd frontend
73
+
74
+cd src
75
+cp App.jsx App.jsx.orig.`date -I`
76
+sed 's,startsWith("/,startsWith("SUBPATH/,g' -i App.jsx
77
+sed 's,path="/,path="SUBPATH/,g' -i App.jsx
78
+sed 's,location = "/,location = "SUBPATH/,g' -i App.jsx
79
+sed 's,window.location.origin;,"SUBPATH";,' -i App.jsx
80
+
81
+cd views
82
+for d in $(ls *.jsx); do cp $d $d.orig.`date -I`; done
83
+sed 's,pathname = "/,pathname = "SUBPATH/,g' -i *.jsx
84
+sed 's,navigate("/,navigate("SUBPATH/,g' -i *.jsx
85
+sed 's,navigate(`/,navigate(`SUBPATH/,g' -i *.jsx
86
+sed 's,href="/,href="SUBPATH/,g' -i *.jsx
87
+sed 's,path = "/,path = "SUBPATH/,g' -i *.jsx
88
+sed 's,link={"/,link={"SUBPATH/,g' -i *.jsx
89
+sed 's,to="/,to="SUBPATH/,g' -i *.jsx
90
+sed 's,to={"/,to={"SUBPATH/,g' -i *.jsx
91
+sed 's,${window.location.origin},SUBPATH,g' -i *.jsx
92
+cd ..
93
+
94
+cd components
95
+for d in $(ls *.jsx); do cp $d $d.orig.`date -I`; done
96
+sed 's,to="/,to="SUBPATH/,g' -i *.jsx
97
+sed 's,${window.location.origin},SUBPATH,g' -i *.jsx
98
+cd ../..
99
+
100
+cd confd/templates
101
+cp nginx.conf nginx.conf.orig.`date -I`
102
+sed 's,location / {,rewrite ^SUBPATH(/api/v1.*)$ $1 last;\n\n\t\tlocation / {,' -i nginx.conf
103
+cd ../..
104
+
105
+docker build -t ghcr.io/shuffle/shuffle-frontend:latest .
106
+
107
+
108
+cat << EOF
109
+
110
+If you want to reverse proxy shuffle using subpath "SUBPATH" you need to configure
111
+your web server - in this case nginx - to do the reverse proxying by at least adding 
112
+
113
+  location SUBPATH {
114
+    proxy_pass http://snooss-proxy:3421/;
115
+    rewrite SUBPATH/(.*)$ /$1 break;
116
+
117
+    proxy_read_timeout 300s;
118
+    
119
+    # proxy header
120
+    proxy_set_header Host $host;
121
+    proxy_set_header X-Real-IP $remote_addr;
122
+    proxy_set_header X-Forwarded-Proto $scheme;
123
+    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
124
+
125
+    # substitute html content response
126
+    proxy_set_header Accept-Encoding "";        # no compression allowed or sub_filter won't work
127
+    sub_filter 'href="/' 'href="SUBPATH/';
128
+    sub_filter 'href:"/' 'href:"SUBPATH/';
129
+    sub_filter '/static' 'SUBPATH/static';
130
+    sub_filter '/images' 'SUBPATH/images';
131
+    sub_filter_types *;
132
+    sub_filter_once off;
133
+  }
134
+
135
+to your nginx configuration.
136
+EOF
137
+
138
+
139
+exit 0

+ 6 - 0
Shuffle/frontend/entrypoint.sh

1
+#!/usr/bin/env sh
2
+set -eu
3
+
4
+envsubst '${BACKEND_HOSTNAME}' < /etc/nginx/nginx.conf.tmpl > /etc/nginx/nginx.conf
5
+
6
+exec "$@"

+ 74 - 0
Shuffle/frontend/frontend-testing.sh

1
+
2
+SERVER_URL="http://localhost:3000"
3
+
4
+declare -a Routes=($(grep -oP '(?<=path=")[^"]*' src/App.jsx | grep -E '^[a-zA-Z0-9/_:-]+$' | grep -vE '^/$'))
5
+
6
+for i in "${!Routes[@]}"; do
7
+  Routes[$i]="${Routes[$i]#/}"
8
+done
9
+
10
+# # Add all tab routes and unique routes here
11
+Routes+=(
12
+'workflows?tab=org_workflows'
13
+'workflows?tab=my_workflows'
14
+'workflows?tab=all_workflows'
15
+'apps/gmail'
16
+'apis/gmail'
17
+'apps?tab=my_apps'
18
+'apps?tab=all_apps'
19
+'search?tab=org_apps'
20
+'search?tab=my_apps'
21
+'search?tab=workflows'
22
+'search?tab=docs'
23
+'search?tab=creators'
24
+'search?tab=discord'
25
+"admin?tab=organization"
26
+"admin?tab=users"
27
+"admin?tab=app_auth"
28
+"admin?tab=datastore"
29
+"admin?tab=files"
30
+"admin?tab=triggers"
31
+"admin?tab=locations"
32
+"admin?tab=tenants"
33
+"admin?admin_tab=org_config"
34
+"admin?admin_tab=sso"
35
+"admin?admin_tab=notifications"
36
+"admin?admin_tab=billingstats"
37
+"admin?admin_tab=branding(beta)"
38
+)
39
+
40
+# Stop frontend container so it test unpushed changes
41
+docker stop shuffle-frontend
42
+
43
+# # Install all frontend dependencies
44
+yarn add selenium-webdriver --dev && yarn install
45
+echo "Starting frontend..."
46
+
47
+BROWSER=none yarn start &  
48
+SERVER_PID=$!
49
+echo "Frontend started with PID: $SERVER_PID"
50
+
51
+echo "Waiting for 1 minute to ensure the server is fully up..."
52
+sleep 60
53
+
54
+echo "Server is up! Starting Selenium tests..."
55
+
56
+echo "Starting frontend tests..."
57
+node selenium-test.js "$SERVER_URL" "${Routes[@]}"
58
+
59
+TEST_EXIT_CODE=$?
60
+
61
+if [[ $TEST_EXIT_CODE -ne 0 ]]; then
62
+echo "Selenium tests failed. Exiting..."
63
+kill $SERVER_PID
64
+exit 1
65
+fi
66
+
67
+kill $SERVER_PID
68
+echo "Testing complete. See above logs for errors if any."
69
+
70
+# Starting shuffle-frontend container
71
+echo "Starting shuffle-frontend container..."
72
+docker start shuffle-frontend
73
+echo "shuffle-frontend started successfully."
74
+exit 0

+ 18 - 0
Shuffle/frontend/index.html

1
+<!DOCTYPE html>
2
+<html lang="en">
3
+  <head>
4
+    <meta charset="utf-8" />
5
+    <link rel="shortcut icon" href="/favicon.ico" />
6
+    <meta
7
+      name="viewport"
8
+      content="width=device-width, initial-scale=1, shrink-to-fit=no"
9
+    />
10
+    <meta name="theme-color" content="#000000" />
11
+    <link rel="manifest" href="/manifest.json" />
12
+    <title>Shuffle</title>
13
+  </head>
14
+  <body>
15
+    <div id="root"></div>
16
+    <script type="module" src="/src/index.js"></script>
17
+  </body>
18
+</html>

Plik diff jest za duży
+ 15018 - 0
Shuffle/frontend/package-lock.json


+ 126 - 0
Shuffle/frontend/package.json

1
+{
2
+  "name": "shuffler",
3
+  "homepage": "https://shuffler.io",
4
+  "version": "2.0.0",
5
+  "private": true,
6
+  "dependencies": {
7
+    "@codemirror/commands": "^6.2.4",
8
+    "@codemirror/lang-python": "^6.1.3",
9
+    "@emotion/react": "^11.11.1",
10
+    "@emotion/styled": "^11.6.0",
11
+    "@lezer/highlight": "^1.1.3",
12
+    "@metamask/detect-provider": "^1.2.0",
13
+    "@mui/icons-material": "^5.14.0",
14
+    "@mui/material": "^5.14.0",
15
+    "@mui/styles": "^6.1.4",
16
+    "@mui/x-data-grid": "^5.17.11",
17
+    "@mui/x-date-pickers": "^6.11.1",
18
+    "@types/algoliasearch": "^3.34.11",
19
+    "@uiw/codemirror-theme-basic": "^4.21.20",
20
+    "@uiw/codemirror-theme-vscode": "^4.21.20",
21
+    "@uiw/codemirror-themes": "^4.21.9",
22
+    "@uiw/react-codemirror": "^4.21.21",
23
+    "algoliasearch": "^4.8.3",
24
+    "class-transformer": "^0.5.1",
25
+    "codemirror": "^6.0.1",
26
+    "cpx": "^1.5.0",
27
+    "cytoscape": "^3.29.2",
28
+    "cytoscape-bubblesets": "^4.1.0",
29
+    "cytoscape-clipboard": "^2.2.1",
30
+    "cytoscape-cose-bilkent": "^4.1.0",
31
+    "cytoscape-cxtmenu": "^3.4.0",
32
+    "cytoscape-edgehandles": "^3.5.1",
33
+    "cytoscape-grid-guide": "~2.3.3",
34
+    "cytoscape-layers": "^3.1.0",
35
+    "cytoscape-node-html-label": "^1.2.2",
36
+    "cytoscape-panzoom": "^2.5.3",
37
+    "cytoscape-undo-redo": "^1.3.3",
38
+    "dayjs": "^1.11.10",
39
+    "dotenv": "^6.1.0",
40
+    "downshift": "^3.4.8",
41
+    "express": "^4.17.1",
42
+    "express-useragent": "^1.0.15",
43
+    "github-markdown-css": "^3.0.1",
44
+    "i18next": "^22.3.0",
45
+    "i18next-browser-languagedetector": "^7.0.1",
46
+    "i18next-chained-backend": "^4.2.0",
47
+    "i18next-http-backend": "^2.1.1",
48
+    "i18next-localstorage-backend": "^4.1.0",
49
+    "i18next-xhr-backend": "^3.2.2",
50
+    "import": "0.0.6",
51
+    "is-plain-obj": "^4.1.0",
52
+    "json-bigint": "^1.0.0",
53
+    "match-sorter": "^6.3.1",
54
+    "md5-file": "^4.0.0",
55
+    "mui-chips-input": "^2.1.3",
56
+    "mui-nested-menu": "^3.2.1",
57
+    "react": "^18.3.1",
58
+    "react-ace": "^10.1.0",
59
+    "react-alice-carousel": "^2.6.4",
60
+    "react-avatar-editor": "^11.1.0",
61
+    "react-beforeunload": "^2.2.1",
62
+    "react-cookie": "^4.0.1",
63
+    "react-cytoscapejs": "^2.0.0",
64
+    "react-device-detect": "^2.2.3",
65
+    "react-dom": "^18.2.0",
66
+    "react-draggable": "^4.4.5",
67
+    "react-driftjs": "^1.2.2",
68
+    "react-dropzone": "^14.2.3",
69
+    "react-ga4": "^2.0.0",
70
+    "react-hotkeys": "^2.0.0",
71
+    "react-instantsearch-dom": "^6.28.0",
72
+    "react-json-pretty": "^2.2.0",
73
+    "react-json-view-ssr": "^1.19.1",
74
+    "react-markdown": "^8.0.7",
75
+    "react-powerhooks": "^0.0.7",
76
+    "react-router": "^6.14.1",
77
+    "react-router-dom": "^6.14.1",
78
+    "react-social-icons": "^5.15.0",
79
+    "react-toastify": "^9.1.3",
80
+    "reaviz": "^14.9.7",
81
+    "rehype-raw": "^7.0.0",
82
+    "remark-gfm": "^3.0.1",
83
+    "remark-html": "^16.0.1",
84
+    "remark-images": "^4.0.0",
85
+    "remark-rehype": "^11.0.0",
86
+    "rsuite": "^5.23.0",
87
+    "search-insights": "^2.2.1",
88
+    "shellwords": "^1.0.1",
89
+    "simplebar": "^4.2.3",
90
+    "styled-components": "^4.4.1",
91
+    "uuid": "^13.0.0",
92
+    "yaml": "^1.10.0",
93
+    "yamljs": "^0.3.0",
94
+    "zone.js": "^0.15.1"
95
+  },
96
+  "scripts": {
97
+    "start": "vite",
98
+    "build": "vite build",
99
+    "preview": "vite preview",
100
+    "lint": "eslint 'src/**/*.{tsx,ts,js,jsx}'",
101
+    "lint_file": "eslint 'src/views/AngularWorkflow.jsx'"
102
+  },
103
+  "browserslist": [
104
+    ">0.2%",
105
+    "not dead",
106
+    "not ie <= 11",
107
+    "not op_mini all"
108
+  ],
109
+  "devDependencies": {
110
+    "@babel/cli": "^7.22.10",
111
+    "@babel/core": "^7.22.10",
112
+    "@babel/plugin-proposal-export-default-from": "^7.22.5",
113
+    "@babel/preset-flow": "^7.22.5",
114
+    "@babel/preset-react": "^7.22.5",
115
+    "@vitejs/plugin-react": "^4.2.1",
116
+    "babel-loader": "^8.0.5",
117
+    "babel-plugin-css-modules-transform": "^1.6.2",
118
+    "babel-plugin-react-css-modules": "^5.2.6",
119
+    "babel-plugin-transform-imports": "^2.0.0",
120
+    "postcss": "^8.4.38",
121
+    "promise-window": "^1.2.1",
122
+    "selenium-webdriver": "^4.29.0",
123
+    "vite": "^5.4.2",
124
+    "webpack-cli": "^5.1.4"
125
+  }
126
+}

Plik diff jest za duży
+ 85 - 0
Shuffle/frontend/public/aiGenerateWorkflowSteps.svg


BIN
Shuffle/frontend/public/favicon.ico


+ 5 - 0
Shuffle/frontend/public/icons/copyIcon.svg

1
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<rect width="24" height="24" fill="#212121" fill-opacity="0.02"/>
3
+<path d="M14 4H7.6C7.17565 4 6.76869 4.16857 6.46863 4.46863C6.16857 4.76869 6 5.17565 6 5.6V18.4C6 18.8243 6.16857 19.2313 6.46863 19.5314C6.76869 19.8314 7.17565 20 7.6 20H17.2C17.6243 20 18.0313 19.8314 18.3314 19.5314C18.6314 19.2313 18.8 18.8243 18.8 18.4V8.8L14 4Z" stroke="#F1F1F1" stroke-linecap="round" stroke-linejoin="round"/>
4
+<path d="M14 4V8.8H18.8" stroke="#F1F1F1" stroke-linecap="round" stroke-linejoin="round"/>
5
+</svg>

+ 4 - 0
Shuffle/frontend/public/icons/deleteIcon.svg

1
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<path d="M5 7.20001H6.6H19.4" stroke="#FD4C62" stroke-linecap="round" stroke-linejoin="round"/>
3
+<path d="M17.7996 7.2V18.4C17.7996 18.8243 17.631 19.2313 17.331 19.5314C17.0309 19.8314 16.624 20 16.1996 20H8.19961C7.77526 20 7.3683 19.8314 7.06824 19.5314C6.76818 19.2313 6.59961 18.8243 6.59961 18.4V7.2M8.99961 7.2V5.6C8.99961 5.17565 9.16818 4.76869 9.46824 4.46863C9.7683 4.16857 10.1753 4 10.5996 4H13.7996C14.224 4 14.6309 4.16857 14.931 4.46863C15.231 4.76869 15.3996 5.17565 15.3996 5.6V7.2" stroke="#FD4C62" stroke-linecap="round" stroke-linejoin="round"/>
4
+</svg>

Plik diff jest za duży
+ 4 - 0
Shuffle/frontend/public/icons/detection.svg


Plik diff jest za duży
+ 5 - 0
Shuffle/frontend/public/icons/docker copy.svg


Plik diff jest za duży
+ 5 - 0
Shuffle/frontend/public/icons/docker.svg


+ 4 - 0
Shuffle/frontend/public/icons/documentation.svg

1
+<svg width="14" height="17" viewBox="0 0 14 17" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<path d="M7.75 1H2.5C2.10218 1 1.72064 1.15804 1.43934 1.43934C1.15804 1.72064 1 2.10218 1 2.5V14.5C1 14.8978 1.15804 15.2794 1.43934 15.5607C1.72064 15.842 2.10218 16 2.5 16H11.5C11.8978 16 12.2794 15.842 12.5607 15.5607C12.842 15.2794 13 14.8978 13 14.5V6.25L7.75 1Z" stroke="#C8C8C8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
3
+<path d="M7.75 1V6.25H13" stroke="#C8C8C8" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
4
+</svg>

Plik diff jest za duży
+ 6 - 0
Shuffle/frontend/public/icons/downloadIcon.svg


+ 3 - 0
Shuffle/frontend/public/icons/editIcon.svg

1
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<path d="M16.1038 4.66848C16.3158 4.45654 16.5674 4.28843 16.8443 4.17373C17.1212 4.05903 17.418 4 17.7177 4C18.0174 4 18.3142 4.05903 18.5911 4.17373C18.868 4.28843 19.1196 4.45654 19.3315 4.66848C19.5435 4.88041 19.7116 5.13201 19.8263 5.40891C19.941 5.68582 20 5.9826 20 6.28232C20 6.58204 19.941 6.87882 19.8263 7.15573C19.7116 7.43263 19.5435 7.68423 19.3315 7.89617L8.43807 18.7896L4 20L5.21038 15.5619L16.1038 4.66848Z" stroke="#F1F1F1" stroke-linecap="round" stroke-linejoin="round"/>
3
+</svg>

+ 4 - 0
Shuffle/frontend/public/icons/expandMoreIcon.svg

1
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<circle cx="12" cy="12" r="12" transform="matrix(-1 0 0 1 24 0)" fill="#2F2F2F"/>
3
+<path d="M8 6L14 12L8 18" stroke="#F1F1F1" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
4
+</svg>

Plik diff jest za duży
+ 84 - 0
Shuffle/frontend/public/icons/k8s.svg


+ 5 - 0
Shuffle/frontend/public/icons/workflow-page/buildIcon-dark.svg

1
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<path d="M2.59995 4.7C1.44015 4.7 0.499951 3.7598 0.499951 2.6C0.499951 1.4402 1.44015 0.5 2.59995 0.5C3.75975 0.5 4.69995 1.4402 4.69995 2.6C4.69995 3.7598 3.75975 4.7 2.59995 4.7Z" stroke="#1A1A1A" stroke-linecap="round" stroke-linejoin="round"/>
3
+<path d="M12.4 14.4998C11.2402 14.4998 10.3 13.5596 10.3 12.3998C10.3 11.24 11.2402 10.2998 12.4 10.2998C13.5598 10.2998 14.5 11.24 14.5 12.3998C14.5 13.5596 13.5598 14.4998 12.4 14.4998Z" stroke="#1A1A1A" stroke-linecap="round" stroke-linejoin="round"/>
4
+<path d="M4.69995 2.6001H5.52832C6.62175 2.6001 7.5125 3.47821 7.52811 4.57153L7.61179 10.4287C7.62741 11.522 8.51815 12.4001 9.61158 12.4001H10.3" stroke="#1A1A1A"/>
5
+</svg>

+ 5 - 0
Shuffle/frontend/public/icons/workflow-page/buildIcon.svg

1
+<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<path d="M2.3001 4.1C1.30598 4.1 0.500098 3.29411 0.500098 2.3C0.500098 1.30589 1.30598 0.5 2.3001 0.5C3.29421 0.5 4.1001 1.30589 4.1001 2.3C4.1001 3.29411 3.29421 4.1 2.3001 4.1Z" stroke="#F1F1F1" stroke-linecap="round" stroke-linejoin="round"/>
3
+<path d="M10.7002 12.5001C9.70613 12.5001 8.90024 11.6943 8.90024 10.7001C8.90024 9.70603 9.70613 8.90015 10.7002 8.90015C11.6944 8.90015 12.5002 9.70603 12.5002 10.7001C12.5002 11.6943 11.6944 12.5001 10.7002 12.5001Z" stroke="#F1F1F1" stroke-linecap="round" stroke-linejoin="round"/>
4
+<path d="M4.1001 2.2998H4.52847C5.62189 2.2998 6.51264 3.17792 6.52826 4.27124L6.59193 8.72837C6.60755 9.82169 7.4983 10.6998 8.59173 10.6998H8.9001" stroke="#F1F1F1"/>
5
+</svg>

+ 5 - 0
Shuffle/frontend/public/icons/workflow-page/comment-dark.svg

1
+<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<path d="M3.33325 9.99992C3.33325 6.31802 6.31802 3.33325 9.99992 3.33325V3.33325C13.6818 3.33325 16.6666 6.31802 16.6666 9.99992V14.2423C16.6666 14.6365 16.6666 14.8336 16.6389 14.9982C16.4975 15.8389 15.8389 16.4975 14.9982 16.6389C14.8336 16.6666 14.6365 16.6666 14.2423 16.6666H9.99992C6.31802 16.6666 3.33325 13.6818 3.33325 9.99992V9.99992Z" stroke="#1A1A1A"/>
3
+<path d="M7.5 10L12.5 10" stroke="#1A1A1A" stroke-linecap="round" stroke-linejoin="round"/>
4
+<path d="M10 7.5L10 12.5" stroke="#1A1A1A" stroke-linecap="round" stroke-linejoin="round"/>
5
+</svg>

+ 5 - 0
Shuffle/frontend/public/icons/workflow-page/comment.svg

1
+<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+<path d="M2.68579 8.05757C2.68579 5.09082 5.09082 2.68579 8.05757 2.68579V2.68579C11.0243 2.68579 13.4293 5.09082 13.4293 8.05757V11.476C13.4293 11.7936 13.4293 11.9524 13.407 12.085C13.2931 12.7624 12.7624 13.2931 12.085 13.407C11.9524 13.4293 11.7936 13.4293 11.476 13.4293H8.05757C5.09082 13.4293 2.68579 11.0243 2.68579 8.05757V8.05757Z" stroke="#9E9E9E" stroke-width="0.805766"/>
3
+<path d="M6.04321 8.05762L10.072 8.05762" stroke="#9E9E9E" stroke-width="0.805766" stroke-linecap="round" stroke-linejoin="round"/>
4
+<path d="M8.05762 6.04321L8.05762 10.072" stroke="#9E9E9E" stroke-width="0.805766" stroke-linecap="round" stroke-linejoin="round"/>
5
+</svg>

+ 0 - 0
Shuffle/frontend/public/icons/workflow-page/delete-dark.svg


Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików

tum/recycle_server - Gogs: Simplico Git Service

Sin descripción

tum e5d90dc5de user profile hace 1 año
..
recycle f47643bdfd models hace 1 año
recycle_core f47643bdfd models hace 1 año
tracking 058f31828c first commit hace 1 año
userprofile e5d90dc5de user profile hace 1 año
manage.py 058f31828c first commit hace 1 año
tracking_mockup.py 058f31828c first commit hace 1 año
tracking_mockup2.py 058f31828c first commit hace 1 año