Initial open source commit

This commit is contained in:
frikky
2020-05-11 19:17:35 +02:00
commit f3587ac821
205 changed files with 63293 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
# Default execution environment for workers
ORG_ID=Shuffle
ENVIRONMENT_NAME=Shuffle
# Other configs
BACKEND_HOSTNAME=shuffle-backend
BACKEND_PORT=5010
OUTER_HOSTNAME=192.168.3.6
+19
View File
@@ -0,0 +1,19 @@
*node_modules/
*build/
*.lock
*.swo
*.swp
*.swn
*__pycache__*
Shuffle-*.json
functions/generated_apps
*.zip
*.png
*.jpeg
*.jpg
backend/onprem/app_sdk/apps
*test.py
+120
View File
@@ -0,0 +1,120 @@
# Shuffle
[Shuffler](https://shuffler.io) is an automation platform for your security stack. It leverages docker for scaling and OpenAPI for integrations. It has the possibility to run across multiple isolated environments, and gives you powerful tools to track progress.
## Try it
Check out the [installation guide](https://github.com/frikky/shuffle/blob/master/install-guide.md)
## Documentation
Documentation can be found on https://shuffler.io/docs/about or in your own instance. Currently lacking:
* API documentation
* Updates after migrating from SaaS to open source
## Features
* Premade workflows for Email, TheHive, C rtex MISP
* Premade apps for a number of security tools
* Simple workflow editor
* App creator for [OpenAPI](https://github.com/frikky/OpenAPI-security-definitions)
* Easy to learn Python library for custom apps
## License
Shuffle is an open source and free software released under the AGPL (Affero General Public License).
### Setup - Local
Frontend - requires [npm](https://nodejs.org/en/download/)/[yarn](https://yarnpkg.com/lang/en/docs/install/#debian-stable)/your preferred manager. Runs independently from backend - edit frontend/src/App.yaml to change from localhost to prod setting.
```bash
cd frontend
npm i
npm start
```
Backend - API calls - requires [>=go1.13](https://golang.org/dl/) and [gcloud](https://cloud.google.com/sdk/install)
```bash
cd backend/go-app
go build
sudo apt -y update && sudo apt -y upgrade && sudo apt install -y google-cloud-sdk-app-engine-python google-cloud-sdk-app-engine-python google-cloud-sdk-datastore-emulator google-cloud-sdk-app-engine-go
go run *.go
```
### Project overview
Below is the folder structure with a short explanation
```bash
├── README.md # :)
├── deploy.sh # Simple oneliner script to build and deploy the code to gcloud
├── backend # Contains directly backend related code. Go with sh tests
├── frontend # Contains frontend code. ReactJS and cytoscape. Horrible code :)
├── app_gen # Contains code generation projects for OpenAPI or PythonLib -> Shuffler app
├── functions # Contains google cloud function code mainly.
│   ├── apps # Some of the existing apps, manually made mostly
│   ├── generated_apps # Some of the autogenerated apps
│   ├── newworker # The worker that handles a workflow as a google cloud function
│   ├── static_baseline.py # Static code used by stitcher.go to generate code
│   ├── stitcher.go # Attempts to stitch together an app and deploy it to cloud functions and (TBD: Docker hub)
│   └── triggers # Custom triggers used in https://shuffler.io/workflows
│   ├── onprem # All code for onprem solutions (https://shuffler.io/docs/hybrid for short doc) _mostly_ reflects google cloud. Should be deprecated somehow and use the same code.
├── openintegrationhub # Here to remind me that openintegrationhub is a thing
├── legacy # Legacy README. Contains A LOT of useful information about what I found with WALKOFF
└── tmp # Some legacy code, not yet ready to be removed
```
# Architecture
A basic image of how everything fits together, including legacy (left side)
![](architecture.png)
# Technology
GCP was chosen because why not use the best thingies. "Serverless" \o/
```bash
├── languages
│   ├── Go # I like go, which is why go.
│   ├── Python3.7 # 3.7 specifically because of f-strings and 2.7 deprecation in 2020
│   ├── Javascript # Frontend stuff. Uses ReactJS + Cytoscape for visualization
│   ├── sh/Bash # Basic testing and some deployment stuff
├── gcloud
│   ├── appengine # Hosting frontend and backend. Currently on a free plan which is nice :)
│   ├── cloud functions # Runs the "apps", "triggers" and other things
│   ├── datastore # Database - TODO before live: Move to firebase
│   ├── storage # Save datablobs and information before deployment
│   ├── pubsub # Used to instantly run cloud functions
│   ├── scheduler # Schedules can be triggers
├── onprem
│   ├── Docker # Runs the same cloud functions. I didn't like the thought of proxies
```
# Current focus(es) AKA todo
1. Make workflows work 99%+ of the time. This is a challenge with onprem + cloud stuff. Cloud sometimes breaks currently because of workers
2. Add user run statistics (e.g. how many runs of each workflow, how many failures etc.)
3. X - Fix OpenAPI app generator
4. Fix error overview in workflows
5. X - Better GUI (improved, but not good)
6. Have default workflows
# How to Add a trigger / custom thing
1. Add it to TriggersView in AngularWorkflow.js
2. Add it to RightSideBar for trigger
# Migration
There will be a major overhaul to the backend specifically. I'm currently moving and updating the following:
- Create dockerfiles and a single runscript
- * App creator - (Cloud function -> Docker)
- * Workflows - Run workflows locally
- * App list - IMPORT EXISTING APPS
- * Dockerfiles - Load the ones that are in workflows with a new version
- * Docker-compose- Frontend, backend, db & orborus
- * Configuration - Write setup documentation - Did for docker
- Workflows - IMPORT DEFAULT WORKFLOWS - Create some towards e.g. TheHive & MISP.
- Documentation - General documentation /docs rewrite
- API doc - 1. In Shuffle. 2. In e.g. python
- Remove orborus? Can deploy straight, but that would be weird.
- Add secret to orborus
- Change workflow name
- Remove registration and add user screen
- Add external and internal hostname for orborus & worker
```
# 1. export DATASTORE_EMULATOR_HOST=0.0.0.0:8000
# 2. docker run -p 8000:8000 google/cloud-sdk gcloud beta emulators datastore start --project=shuffle --host-port 0.0.0.0:8000 --no-store-on-disk
```
* Mail: Use appengine and connect to sendmail
### Migration issues:
* Some workflows where items have multiple parents don't work.
* Fix dummy.json (GCP config) - bypass this somehow.
@@ -0,0 +1,235 @@
components:
schemas:
tmp0:
properties:
cb_version:
type: string
company_name:
type: string
copied_mod_len:
type: string
digsig_issuer:
type: string
digsig_prog_name:
type: string
digsig_publisher:
type: string
digsig_result:
type: string
digsig_result_code:
type: string
digsig_sign_time:
type: string
digsig_subject:
type: string
endpoint:
type: string
event_partition_id:
type: string
facet_id:
type: string
file_desc:
type: string
file_version:
type: string
group:
type: string
host_count:
type: string
internal_name:
type: string
is_64bit:
type: string
is_executable_image:
type: string
last_seen:
type: string
legal_copyright:
type: string
md5:
type: string
observed_filename:
type: string
orig_mod_len:
type: string
original_filename:
type: string
os_type:
type: string
product_name:
type: string
product_version:
type: string
server_added_timestamp:
type: string
signed:
type: string
timestamp:
type: string
watchlists:
type: string
type: object
tmp1:
properties:
message:
type: string
type: object
tmp2:
properties:
childproc_count:
type: string
cmdline:
type: string
comms_ip:
type: string
crossproc_count:
type: string
emet_config:
type: string
emet_count:
type: string
filemod_count:
type: string
filtering_known_dlls:
type: string
group:
type: string
host_type:
type: string
hostname:
type: string
id:
type: string
interface_ip:
type: string
last_server_update:
type: string
last_update:
type: string
modload_count:
type: string
netconn_count:
type: string
os_type:
type: string
parent_id:
type: string
parent_name:
type: string
parent_pid:
type: string
parent_unique_id:
type: string
path:
type: string
process_md5:
type: string
process_name:
type: string
process_pid:
type: string
processblock_count:
type: string
regmod_count:
type: string
segment_id:
type: string
sensor_id:
type: string
start:
type: string
terminated:
type: string
unique_id:
type: string
username:
type: string
type: object
tmp3:
properties:
message:
type: string
type: object
securitySchemes: {}
info:
contact:
email: frikky@shuffler.io
name: '@frikkylikeme'
url: https://twitter.com/frikkylikeme
description: Automated generation of Carbon Black Response
title: Carbon Black Response
version: 1.0.0
openapi: 3.0.2
paths:
tmp0:
post:
description: Carbon Black Response Binary Search
parameters:
- description: Query
in: query
name: Query
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp0'
description: Successful request
summary: Carbon Black Response Binary Search
tmp1:
post:
description: Carbon Black Response Isolate Sensor
parameters:
- description: Hostname of a sensor to isolate.
in: query
name: Hostname
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp1'
description: Successful request
summary: Carbon Black Response Isolate Sensor
tmp2:
post:
description: Carbon Black Response Process Search
parameters:
- description: Query
in: query
name: Query
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp2'
description: Successful request
summary: Carbon Black Response Process Search
tmp3:
post:
description: Carbon Black Response Unisolate Sensor
parameters:
- description: Hostname of a sensor to unisolate.
in: query
name: Hostname
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp3'
description: Successful request
summary: Carbon Black Response Unisolate Sensor
@@ -0,0 +1,559 @@
components:
schemas:
tmp0:
properties:
outcome:
type: string
type: object
tmp1:
properties:
allRansomwareProcessesSuspended:
type: string
closeTime:
type: string
closerName:
type: string
creationTime:
type: string
customClassification:
type: string
decisionFeature:
type: string
detectionType:
type: string
elementDisplayName:
type: string
hasRansomwareSuspendedProcesses:
type: string
isBlocked:
type: string
malopActivityTypes:
type: string
malopLastUpdateTime:
type: string
malopStartTime:
type: string
managementStatus:
type: string
rootCauseElementNames:
type: string
rootCauseElementTypes:
type: string
type: object
tmp2:
properties:
blocking:
type: string
direction:
type: string
domain:
type: string
ipAddress:
type: string
ipAddressString:
type: string
lastUpdated:
type: string
port:
type: string
ruleId:
type: string
type: object
tmp3:
properties:
blacklistClassification:
type: string
classificationLink:
type: string
containsTorDomain:
type: string
domainClassificationSuspicion:
type: string
domainNameUniqueId:
type: string
elementDisplayName:
type: string
everResolvedDomain:
type: string
everResolvedSecondLevelDomain:
type: string
extendedDisplayId:
type: string
federationSegments:
type: string
getIpDiscoveryDomainList:
type: string
hasMalops:
type: string
hasResolvedClassificationEvidence:
type: string
hasSuspicions:
type: string
indifferentClassification:
type: string
isDomainMalicious:
type: string
isInIpDiscoveryDomainList:
type: string
isInternalDomain:
type: string
isInternalDomainByName:
type: string
isReverseLookup:
type: string
isTorrentDomain:
type: string
lookupDomainClassification:
type: string
maliciousClassification:
type: string
maliciousClassificationString:
type: string
maliciousClassificationType:
type: string
malwareClassification:
type: string
name:
type: string
relatedToMalop:
type: string
rootCauseKey:
type: string
secondLevelDomain:
type: string
sinkholedClassification:
type: string
sinkholedClassificationEvidence:
type: string
topLevelDomain:
type: string
unknownClassification:
type: string
unresolvedClassification:
type: string
unwantedClassification:
type: string
whitelistClassification:
type: string
type: object
tmp4:
properties:
attemptExecutionProcessSuspicion:
type: string
attributes:
type: string
blackListClassification:
type: string
canonizedPath:
type: string
classificationLink:
type: string
correctedPath:
type: string
createdTime:
type: string
detectionEventData:
type: string
dualExtensionName:
type: string
elementDisplayName:
type: string
extendedDisplayId:
type: string
extension:
type: string
extensionRecord:
type: string
extensionType:
type: string
externalProductClassification:
type: string
externalProductClassificationIsSigned:
type: string
externalProductClassificationSignatureVerificationStatus:
type: string
externalProductClassificationSignatureVerified:
type: string
externalProductClassificationSignatureVerifiedByVerificationStatus:
type: string
externalProductClassificationType:
type: string
federationSegments:
type: string
fileHasSystem32SubDirectories:
type: string
fileHasSystemSubDirectories:
type: string
fileHashUniqueId:
type: string
fileInSystemPath:
type: string
fileReputationSuspicion:
type: string
fileReputationSuspicionDecision:
type: string
fileVersionSuspicionDecision:
type: string
getNetworkScannersNames:
type: string
getToolsForUnusualNetworkEvidence:
type: string
hackingToolClassification:
type: string
hasAutorun:
type: string
hasClassification:
type: string
hasInternalName:
type: string
hasLegitClassification:
type: string
hasMalops:
type: string
hasNonLegitClassification:
type: string
hasNonLegitClassificationEvidence:
type: string
hasProductClassification:
type: string
hasRansomwareClassificationSubType:
type: string
hasSuspicions:
type: string
identifiedProduct:
type: string
indifferentClassification:
type: string
isDocument:
type: string
isExecutable:
type: string
isFromRemovableDevice:
type: string
isFromTemp:
type: string
isNoTypeFoundClassification:
type: string
isPEFile:
type: string
isProcessImageFile:
type: string
isScreenSaver:
type: string
isSigned:
type: string
isSuspicious:
type: string
lastDetectionEventData:
type: string
maliciousClassification:
type: string
maliciousClassificationString:
type: string
maliciousClassificationType:
type: string
maliciousToolClassification:
type: string
malwareClassification:
type: string
malwareClassificationEvidence:
type: string
malwareType:
type: string
md5:
type: string
md5String:
type: string
missingInterperterSectionValue:
type: string
modifiedTime:
type: string
name:
type: string
nameWithoutExtension:
type: string
path:
type: string
peSignedAndVerified:
type: string
productClassificationType:
type: string
productType:
type: string
profileId:
type: string
ransomwareClassification:
type: string
reasonSignatureVerificationStatus:
type: string
relatedToMalop:
type: string
reportedByAntiMalwareEvidence:
type: string
reportedByAntiMalwareSuspicion:
type: string
rootCauseKey:
type: string
sha1:
type: string
sha1String:
type: string
signatureVerified:
type: string
signatureVerifiedByVerificationStatus:
type: string
signatureVerifiedInternalOrExternal:
type: string
signedByApple:
type: string
signedByLinux:
type: string
signedByMicrosoft:
type: string
signedByOperatingSystem:
type: string
signedInternalOrExternal:
type: string
size:
type: string
suspiciousClassification:
type: string
suspiciousScreenSaverCondition:
type: string
unknownClassification:
type: string
unsignedPeFileEvidence:
type: string
unwantedClassification:
type: string
whitelistClassification:
type: string
type: object
tmp5:
properties:
address:
type: string
addressInternalExternalLocal:
type: string
addressString:
type: string
blackListClassification:
type: string
countryCode:
type: string
countryName:
type: string
countryNameOrNotExternalType:
type: string
elementDisplayName:
type: string
extendedDisplayId:
type: string
federationSegments:
type: string
geolocationLookup:
type: string
hasMalops:
type: string
hasSuspicions:
type: string
isDynamicConfiguration:
type: string
isExternalAddress:
type: string
isInternalAddress:
type: string
isLocalAddress:
type: string
latitude:
type: string
longitude:
type: string
lookupIpClassification:
type: string
maliciousClassification:
type: string
maliciousClassificationType:
type: string
rootCauseKey:
type: string
uniqueIpAddressHash:
type: string
version:
type: string
whiteListClassification:
type: string
type: object
securitySchemes: {}
info:
contact:
email: frikky@shuffler.io
name: '@frikkylikeme'
url: https://twitter.com/frikkylikeme
description: Automated generation of Cyberreason
title: Cyberreason
version: 1.0.0
openapi: 3.0.2
paths:
tmp0:
post:
description: CyberReason Block or Unblock an Item
parameters:
- description: Either 'blacklist' or 'whitelist'
in: query
name: Action
required: true
schema:
type: string
- description: IP, Domain, or Hash CSV values to blacklist or whitelist
in: query
name: Values
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp0'
description: Successful request
summary: CyberReason Block Item
tmp1:
post:
description: CyberReason get alerts from MalOps
parameters: []
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp1'
description: Successful request
summary: CyberReason Get MalOps Alerts
tmp2:
post:
description: CyberReason isolate a host by port
parameters:
- description: IP to block
in: query
name: IP
required: true
schema:
type: string
- description: Direction to block traffic, one of 'ALL', 'INCOMING', 'OUTGOING'
in: query
name: Direction to block
required: true
schema:
type: string
- description: Port to block
in: query
name: Port
required: true
schema:
type: string
- description: If true will isolate, false will remove from isolation
in: query
name: Block
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp2'
description: Successful request
summary: CyberReason Isolate Host
tmp3:
post:
description: Check CyberReason for a domain
parameters:
- description: Limit of results to return
in: body
name: Limit
required: false
schema:
type: string
- description: Timeout
in: body
name: Timeout (ms)
required: false
schema:
type: string
- description: Domain to search for
in: query
name: Domain
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp3'
description: Successful request
summary: CyberReason Query for Domain
tmp4:
post:
description: Check CyberReason for a hash
parameters:
- description: Limit of results to return
in: body
name: Limit
required: false
schema:
type: string
- description: Timeout
in: body
name: Timeout (ms)
required: false
schema:
type: string
- description: Hash to search for
in: query
name: Hash
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp4'
description: Successful request
summary: CyberReason Query for Hash
tmp5:
post:
description: Check CyberReason for a IP
parameters:
- description: Limit of results to return
in: body
name: Limit
required: false
schema:
type: string
- description: Timeout
in: body
name: Timeout (ms)
required: false
schema:
type: string
- description: IP to search for
in: query
name: IP
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp5'
description: Successful request
summary: CyberReason Query for IP
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,166 @@
components:
schemas:
tmp0:
properties:
bid:
type: string
cve:
type: string
description:
type: string
msb:
type: string
osvdb:
type: string
source:
type: string
type: object
tmp1:
properties:
data:
type: string
domains:
type: string
hostnames:
type: string
ip:
type: string
location.area_code:
type: string
location.city:
type: string
location.country_code:
type: string
location.country_name:
type: string
location.dma_code:
type: string
location.latitude:
type: string
location.longitude:
type: string
location.postal_code:
type: string
location.region_code:
type: string
org:
type: string
os:
type: string
port:
type: string
transport:
type: string
type: object
tmp2:
properties:
devicetype:
type: string
domains:
type: string
hostnames:
type: string
ip_str:
type: string
isp:
type: string
location.area_code:
type: string
location.city:
type: string
location.country_code:
type: string
location.country_name:
type: string
location.dma_code:
type: string
location.latitude:
type: string
location.longitude:
type: string
location.postal_code:
type: string
org:
type: string
os:
type: string
port:
type: string
product:
type: string
timestamp:
type: string
title:
type: string
type: object
securitySchemes: {}
info:
contact:
email: frikky@shuffler.io
name: '@frikkylikeme'
url: https://twitter.com/frikkylikeme
description: Automated generation of Shodan
title: Shodan
version: 1.0.0
openapi: 3.0.2
paths:
tmp0:
post:
description: Search across a variety of data sources for exploits
parameters:
- description: Search query used to search the database of known exploits
in: query
name: Query
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp0'
description: Successful request
summary: Shodan Exploit Search
tmp1:
post:
description: Search all services that have been found on the given host IP
parameters:
- description: Host IP address
in: query
name: IP
required: true
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp1'
description: Successful request
summary: Shodan Host
tmp2:
post:
description: Search the SHODAN database
parameters:
- description: Keyword to search Shodan for
in: query
name: Query
required: true
schema:
type: string
- description: 'Max number of results to return. Default: ''Infinite'''
in: body
name: Limit
required: false
schema:
type: string
responses:
'200':
content:
application/json:
schema:
$ref: '#/components/schemas/tmp2'
description: Successful request
summary: Shodan Search
@@ -0,0 +1,149 @@
components:
schemas:
tmp0:
properties:
bios_uuid:
type: string
fqdn:
type: string
hostname:
type: string
id:
type: string
ipv4:
type: string
ipv6:
type: string
mac_address:
type: string
netbios_name:
type: string
operating_system:
type: string
ssh_fingerprint:
type: string
system_type:
type: string
type: object
tmp1:
properties:
agent_name:
type: string
fqdn:
type: string
id:
type: string
ipv4:
type: string
ipv6:
type: string
last_seen:
type: string
mac_address:
type: string
operating_system:
type: string
type: object
tmp2:
properties:
hostcount:
type: string
name:
type: string
owner:
type: string
policy:
type: string
scan_end:
type: string
scan_start:
type: string
status:
type: string
targets:
type: string
uuid:
type: string
type: object
tmp4:
properties:
count:
type: string
plugin_family:
type: string
plugin_name:
type: string
severity:
type: string
vulnerability_state:
type: string
type: object
securitySchemes: {}
info:
contact:
email: frikky@shuffler.io
name: '@frikkylikeme'
url: https://twitter.com/frikkylikeme
description: Automated generation of Tenable Tenable.io
title: Tenable Tenable.io
version: 1.0.0
openapi: 3.0.2
paths:
tmp0:
post:
description: Returns information about the specified asset.
parameters:
- description: The UUID of the asset.
in: query
name: Asset UUID
required: true
schema:
type: string
responses:
'200':
description: Successful request
summary: Tenable.io Asset Info
tmp1:
post:
description: Returns a list of up to 5000 assets.
parameters: []
responses:
'200':
description: Successful request
summary: Tenable.io List Assets
tmp2:
post:
description: Returns details for the given scan.
parameters:
- description: The ID of the scan.
in: query
name: Scan ID
required: true
schema:
type: string
responses:
'200':
description: Successful request
summary: Tenable.io Scan Details
tmp3:
post:
description: Launches a scan.
parameters:
- description: The ID of the scan.
in: body
name: Scan ID
required: true
schema:
type: string
responses:
'200':
description: Successful request
summary: Tenable.io Scan Launch
tmp4:
post:
description: Retrieves a list of recorded vulnerabilities.
parameters: []
responses:
'200':
description: Successful request
summary: Tenable.io Vulnerabilities
+109
View File
@@ -0,0 +1,109 @@
import json
import yaml
items = []
openapi = {
"openapi": "3.0.2",
"info": {
"title": "MISP",
"description": "MISP API generated from the misp book: https://github.com/MISP/misp-book/blob/master/automation/README.md",
"version": "1.0.0",
"contact": {
"name": "@frikkylikeme",
"url": "https://twitter.com/frikkylikeme",
"email": "frikky@shuffler.io"
}
},
"paths": {},
"components": {
"schemas": {},
"securitySchemes": {
"ApiKeyAuth": {
"type": "apikey",
"in": "header",
"name": "Authorization",
}
},
}
}
with open("misp.txt", "r") as tmp:
newitem = {}
recorditem = False
counter = 0
itemsplit = tmp.read().split("\n")
for item in itemsplit:
counter += 1
if item.startswith("### ") and "/" in item:
try:
path = item.split(" ")[2]
method = item.split(" ")[1].lower()
newitem = {
"path": path,
"method": method,
}
try:
openapi["paths"][path][method] = {}
except KeyError:
openapi["paths"][path] = {}
openapi["paths"][path][method] = {}
except IndexError:
newitem = {}
continue
recorditem = True
#print(newitem)
if not recorditem:
continue
if "Description" in item:
openapi["paths"][newitem["path"]][newitem["method"]]["description"] = itemsplit[counter+1]
elif "URL Arguments" in item:
parameters = []
innercnt = 0
openapi["paths"][newitem["path"]][newitem["method"]]["parameters"] = []
while True:
curline = itemsplit[counter+1+innercnt]
if "#" in curline:
break
innercnt += 1
if not curline:
continue
print(curline)
parameters.append({
"description": curline.split(" ")[1],
"in": "query",
"name": curline.split(" ")[1],
"required": True,
"schema": {"type": "string"},
})
openapi["paths"][newitem["path"]][newitem["method"]]["parameters"] = parameters
elif "Output" in item:
# FIXME
innercnt = 0
while True:
curline = itemsplit[counter+1+innercnt]
if "#" in curline:
break
innercnt += 1
if "json" in curline:
continue
#print(curline)
print(json.dumps(openapi, indent=4))
generatedfile = "generated/misp.yaml"
with open(generatedfile, "w+") as tmp:
tmp.write(yaml.dump(openapi))
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+131
View File
@@ -0,0 +1,131 @@
import requests
import yaml
def parse_data(data):
openapi = {
"openapi": "3.0.2",
"info": {
"title": "",
"description": "",
"version": "1.0.0",
"contact": {
"name": "@frikkylikeme",
"url": "https://twitter.com/frikkylikeme",
"email": "frikky@shuffler.io"
}
},
"paths": {},
"components": {
"schemas": {},
"securitySchemes": {},
}
}
category = data["category"]
filename = "%s.yaml" % data["title"].replace(" ", "_").lower()
openapi["info"]["title"] = data["title"]
openapi["info"]["description"] = "Automated generation of %s" % data["title"]
# data["description"]
cnt = 0
for task in data["tasks"]:
method = "post"
openapi["paths"]["tmp%d" % cnt] = {}
openapi["paths"]["tmp%d" % cnt][method] = {
"summary": task["name"],
"description": task["description"],
"parameters": [],
"responses": {
"200": {
"description": "Successful request",
}
},
}
#taskname = task["name"]
#taskdescription = task["description"]
taskcategory = task["family"]
# This doesn't really do much except build the return value structures
for parameter in task["input_parameters"]:
example = parameter["example"]
inVar = "query"
if parameter["type"] == 6:
inVar = "body"
schema = "string"
schemaset = False
if parameter["type"] != 1:
if (parameter["type"] == 7):
schema = "boolean"
schemaset = True
if schema == "string" and schemaset:
print("Should change type: %d" % parameter["type"])
print(task["name"])
print(parameter["name"])
print()
if len(example) == 1:
print("Change to number?")
if example.startswith("{"):
print("Change to json object?")
if example.startswith("["):
print("Change to array object?")
# Not sure how to tackle this
openapi["paths"]["tmp%d" % cnt][method]["parameters"].append({
"in": inVar,
"name": parameter["name"],
"required": parameter["required"],
"description": parameter["description"],
"schema": {"type": schema}
})
if len(task["available_output_variables"]) > 0:
openapi["paths"]["tmp%d" % cnt][method]["responses"]["200"]["content"]: {
"application/json": {
"schema": {
"$ref": "#/components/schemas/tmp%d" % cnt
}
}
}
openapi["components"]["schemas"]["tmp%d" % cnt] = {
"type": "object",
"properties": {},
}
for output in task["available_output_variables"]:
openapi["components"]["schemas"]["tmp%d" % cnt]["properties"][output["key"]] = {"type": "string"}
cnt += 1
#print(openapi)
#print(filename)
return filename, openapi
def dump_data(filename, openapi):
generatedfile = "generated/%s" % filename
with open(generatedfile, "w+") as tmp:
tmp.write(yaml.dump(openapi))
print("Generated %s" % generatedfile)
if __name__ == "__main__":
url = "https://apphub.swimlane.com/api/v1/bundles/cjuspytpz00rh0hpjo5chqg10"
url = "https://apphub.swimlane.com/api/v1/bundles/cjyoy62ch04920lr26id5sr0e"
url = "https://apphub.swimlane.com/api/v1/bundles/cjqrdc2yr02rs0fli6jrosiqb"
url = "https://apphub.swimlane.com/api/v1/bundles/cjqrdat0u01ux0flipb68a0a0"
url = "https://apphub.swimlane.com/api/v1/bundles/cjqrdhbwp07nf0fli23lyb52h"
data = requests.get(url).json()
filename, openapi = parse_data(data)
dump_data(filename, openapi)
+7
View File
@@ -0,0 +1,7 @@
# OpenAPI generator
This contains test code that's been moved to shaffuru/backend/go-app/codegen.go
## Todo:
1. Don't use filesystem, but rather store in GCP
2. Add swagger 2.0 to 3.0 converter
3. Fix body / data parsing
+26
View File
@@ -0,0 +1,26 @@
# Base our app image off of the WALKOFF App SDK image
FROM frikky/shuffle:app_sdk as base
# We're going to stage away all of the bloat from the build tools so lets create a builder stage
FROM base as builder
# Install all alpine build tools needed for our pip installs
RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev
# Install all of our pip packages in a single directory that we can copy to our base image later
RUN mkdir /install
WORKDIR /install
COPY requirements.txt /requirements.txt
RUN pip install --prefix="/install" -r /requirements.txt
# Switch back to our base image and copy in all of our built packages and source code
FROM base
COPY --from=builder /install /usr/local
COPY src /app
# Install any binary dependencies needed in our final image - this can be a lot of different stuff
RUN apk --no-cache add --update libmagic
# Finally, lets run our app!
WORKDIR /app
CMD python app.py --log-level DEBUG
@@ -0,0 +1,3 @@
# No extra requirements needed
requests
urllib3
+387
View File
@@ -0,0 +1,387 @@
package main
import (
"crypto/md5"
"encoding/hex"
"errors"
"fmt"
"github.com/getkin/kin-openapi/openapi3"
"gopkg.in/yaml.v2"
"io"
"io/ioutil"
"log"
"os"
"strings"
)
type WorkflowApp struct {
Name string `json:"name" yaml:"name" required:true datastore:"name"`
IsValid bool `json:"is_valid" yaml:"is_valid" required:true datastore:"is_valid"`
ID string `json:"id" yaml:"id,omitempty" required:false datastore:"id"`
Link string `json:"link" yaml:"link" required:false datastore:"link,noindex"`
AppVersion string `json:"app_version" yaml:"app_version" required:true datastore:"app_version"`
Description string `json:"description" datastore:"description" required:false yaml:"description"`
Environment string `json:"environment" datastore:"environment" required:true yaml:"environment"`
SmallImage string `json:"small_image" datastore:"small_image,noindex" required:false yaml:"small_image"`
LargeImage string `json:"large_image" datastore:"large_image,noindex" yaml:"large_image" required:false`
ContactInfo struct {
Name string `json:"name" datastore:"name" yaml:"name"`
Url string `json:"url" datastore:"url" yaml:"url"`
} `json:"contact_info" datastore:"contact_info" yaml:"contact_info" required:false`
Actions []WorkflowAppAction `json:"actions" yaml:"actions" required:true datastore:"actions"`
Authentication Authentication `json:"authentication" yaml:"authentication" required:false datastore:"authentication"`
}
type AuthenticationParams struct {
Description string `json:"description" datastore:"description" yaml:"description"`
ID string `json:"id" datastore:"id" yaml:"id"`
Name string `json:"name" datastore:"name" yaml:"name"`
Example string `json:"example" datastore:"example" yaml:"example"s`
Value string `json:"value,omitempty" datastore:"value" yaml:"value"`
Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"`
Required bool `json:"required" datastore:"required" yaml:"required"`
}
type Authentication struct {
Required bool `json:"required" datastore:"required" yaml:"required" `
Parameters []AuthenticationParams `json:"parameters" datastore:"parameters" yaml:"parameters"`
}
type AuthenticationStore struct {
Key string `json:"key" datastore:"key"`
Value string `json:"value" datastore:"value"`
}
type WorkflowAppActionParameter struct {
Description string `json:"description" datastore:"description" yaml:"description"`
ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
Name string `json:"name" datastore:"name" yaml:"name"`
Example string `json:"example" datastore:"example" yaml:"example"`
Value string `json:"value" datastore:"value" yaml:"value,omitempty"`
Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"`
ActionField string `json:"action_field" datastore:"action_field" yaml:"actionfield,omitempty"`
Variant string `json:"variant" datastore:"variant" yaml:"variant,omitempty"`
Required bool `json:"required" datastore:"required" yaml:"required"`
Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"`
}
type SchemaDefinition struct {
Type string `json:"type" datastore:"type"`
}
type WorkflowAppAction struct {
Description string `json:"description" datastore:"description"`
ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
Name string `json:"name" datastore:"name"`
NodeType string `json:"node_type" datastore:"node_type"`
Environment string `json:"environment" datastore:"environment"`
Authentication []AuthenticationStore `json:"authentication" datastore:"authentication" yaml:"authentication,omitempty"`
Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters"`
Returns struct {
Description string `json:"description" datastore:"returns" yaml:"description,omitempty"`
ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"`
} `json:"returns" datastore:"returns"`
}
func copyFile(fromfile, tofile string) error {
from, err := os.Open(fromfile)
if err != nil {
return err
}
defer from.Close()
to, err := os.OpenFile(tofile, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return err
}
defer to.Close()
_, err = io.Copy(to, from)
if err != nil {
return err
}
return nil
}
// Builds the base structure for the app that we're making
// Returns error if anything goes wrong. This has to work if
// the python code is supposed to be generated
func buildStructure(swagger *openapi3.Swagger, curHash string) (string, error) {
//log.Printf("%#v", swagger)
// adding md5 based on input data to not overwrite earlier data.
generatedPath := "generated"
identifier := fmt.Sprintf("%s-%s", swagger.Info.Title, curHash)
appPath := fmt.Sprintf("%s/%s", generatedPath, identifier)
os.MkdirAll(appPath, os.ModePerm)
os.Mkdir(fmt.Sprintf("%s/src", appPath), os.ModePerm)
err := copyFile("baseline/Dockerfile", fmt.Sprintf("%s/%s", appPath, "Dockerfile"))
if err != nil {
log.Println("Failed to move Dockerfile")
return appPath, err
}
err = copyFile("baseline/requirements.txt", fmt.Sprintf("%s/%s", appPath, "requirements.txt"))
if err != nil {
log.Println("Failed to move requrements.txt")
return appPath, err
}
return appPath, nil
}
func makePythoncode(name, url, method string, parameters, optionalQueries []string) string {
method = strings.ToLower(method)
queryString := ""
queryData := ""
// FIXME - this might break - need to check if ? or & should be set as query
parameterData := ""
if len(optionalQueries) > 0 {
queryString += ", "
for _, query := range optionalQueries {
queryString += fmt.Sprintf("%s=\"\"", query)
queryData += fmt.Sprintf(`
if %s:
url += f"&%s={%s}"`, query, query, query)
}
}
if len(parameters) > 0 {
parameterData = fmt.Sprintf(", %s", strings.Join(parameters, ", "))
}
// FIXME - add checks for query data etc
data := fmt.Sprintf(` async def %s_%s(self%s%s):
url=f"%s"
%s
return requests.%s(url).text
`, name, method, parameterData, queryString, url, queryData, method)
return data
}
func generateYaml(swagger *openapi3.Swagger) (WorkflowApp, []string, error) {
api := WorkflowApp{}
log.Printf("%#v", swagger.Info)
if len(swagger.Info.Title) == 0 {
return WorkflowApp{}, []string{}, errors.New("Swagger.Info.Title can't be empty.")
}
if len(swagger.Servers) == 0 {
return WorkflowApp{}, []string{}, errors.New("Swagger.Servers can't be empty. Add 'servers':[{'url':'hostname.com'}'")
}
api.Name = swagger.Info.Title
api.Description = swagger.Info.Description
api.IsValid = true
api.Link = swagger.Servers[0].URL // host doesnt exist lol
api.AppVersion = "1.0.0"
api.Environment = "cloud"
api.ID = ""
api.SmallImage = ""
api.LargeImage = ""
// This is the python code to be generated
// Could just as well be go at this point lol
pythonFunctions := []string{}
for actualPath, path := range swagger.Paths {
//log.Printf("%#v", path)
//log.Printf("%#v", actualPath)
// Find the path name and add it to makeCode() param
firstQuery := true
if path.Get != nil {
// What to do with this, hmm
functionName := strings.ReplaceAll(path.Get.Summary, " ", "_")
functionName = strings.ToLower(functionName)
action := WorkflowAppAction{
Description: path.Get.Description,
Name: path.Get.Summary,
NodeType: "action",
Environment: api.Environment,
Parameters: []WorkflowAppActionParameter{},
}
action.Returns.Schema.Type = "string"
baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath)
//log.Println(path.Parameters)
// Parameters: []WorkflowAppActionParameter{},
// FIXME - add data for POST stuff
firstQuery = true
optionalQueries := []string{}
parameters := []string{}
optionalParameters := []WorkflowAppActionParameter{}
if len(path.Get.Parameters) > 0 {
for _, param := range path.Get.Parameters {
curParam := WorkflowAppActionParameter{
Name: param.Value.Name,
Description: param.Value.Description,
Multiline: false,
Required: param.Value.Required,
Schema: SchemaDefinition{
Type: param.Value.Schema.Value.Type,
},
}
if param.Value.Required {
action.Parameters = append(action.Parameters, curParam)
} else {
optionalParameters = append(optionalParameters, curParam)
}
if param.Value.In == "path" {
log.Printf("PATH!: %s", param.Value.Name)
parameters = append(parameters, param.Value.Name)
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
} else if param.Value.In == "query" {
log.Printf("QUERY!: %s", param.Value.Name)
if !param.Value.Required {
optionalQueries = append(optionalQueries, param.Value.Name)
continue
}
parameters = append(parameters, param.Value.Name)
if firstQuery {
baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
firstQuery = false
} else {
baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
firstQuery = false
}
}
}
}
// ensuring that they end up last in the specification
// (order is ish important for optional params) - they need to be last.
for _, optionalParam := range optionalParameters {
action.Parameters = append(action.Parameters, optionalParam)
}
curCode := makePythoncode(functionName, baseUrl, "get", parameters, optionalQueries)
pythonFunctions = append(pythonFunctions, curCode)
api.Actions = append(api.Actions, action)
}
}
return api, pythonFunctions, nil
}
func verifyApi(api WorkflowApp) WorkflowApp {
if api.AppVersion == "" {
api.AppVersion = "1.0.0"
}
return api
}
func dumpPython(basePath, name, version string, pythonFunctions []string) error {
//log.Printf("%#v", api)
log.Printf(strings.Join(pythonFunctions, "\n"))
parsedCode := fmt.Sprintf(`import requests
import asyncio
import json
from walkoff_app_sdk.app_base import AppBase
class %s(AppBase):
"""
Autogenerated class by Shuffler
"""
__version__ = "%s"
app_name = "%s"
def __init__(self, redis, logger, console_logger=None):
self.verify = False
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
super().__init__(redis, logger, console_logger)
%s
if __name__ == "__main__":
asyncio.run(CarbonBlack.run(), debug=True)
`, name, version, name, strings.Join(pythonFunctions, "\n"))
err := ioutil.WriteFile(fmt.Sprintf("%s/src/app.py", basePath), []byte(parsedCode), os.ModePerm)
if err != nil {
return err
}
fmt.Println(parsedCode)
//log.Println(string(data))
return nil
}
func dumpApi(basePath string, api WorkflowApp) error {
//log.Printf("%#v", api)
data, err := yaml.Marshal(api)
if err != nil {
log.Printf("Error with yaml marshal: %s", err)
return err
}
err = ioutil.WriteFile(fmt.Sprintf("%s/api.yaml", basePath), []byte(data), os.ModePerm)
if err != nil {
return err
}
//log.Println(string(data))
return nil
}
func main() {
data := []byte(`{"swagger":"3.0","info":{"title":"hi","description":"you","version":"1.0"},"servers":[{"url":"https://shuffler.io/api/v1"}],"host":"shuffler.io","basePath":"/api/v1","schemes":["https:"],"paths":{"/workflows":{"get":{"responses":{"default":{"description":"default","schema":{}}},"summary":"Get workflows","description":"Get workflows","parameters":[]}},"/workflows/{id}":{"get":{"responses":{"default":{"description":"default","schema":{}}},"summary":"Get workflow","description":"Get workflow","parameters":[{"in":"query","name":"forgetme","description":"Generated by shuffler.io OpenAPI","required":true,"schema":{"type":"string"}},{"in":"query","name":"anotherone","description":"Generated by shuffler.io OpenAPI","required":false,"schema":{"type":"string"}},{"in":"query","name":"hi","description":"Generated by shuffler.io OpenAPI","required":true,"schema":{"type":"string"}},{"in":"path","name":"id","description":"Generated by shuffler.io OpenAPI","required":true,"schema":{"type":"string"}}]}}},"securityDefinitions":{}}`)
hasher := md5.New()
hasher.Write(data)
newmd5 := hex.EncodeToString(hasher.Sum(nil))
swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(data)
if err != nil {
log.Printf("Swagger validation error: %s", err)
os.Exit(3)
}
if strings.Contains(swagger.Info.Title, " ") {
strings.ReplaceAll(swagger.Info.Title, " ", "")
}
basePath, err := buildStructure(swagger, newmd5)
if err != nil {
log.Printf("Failed to build base structure: %s", err)
os.Exit(3)
}
api, pythonfunctions, err := generateYaml(swagger)
if err != nil {
log.Printf("Failed building and generating yaml: %s", err)
os.Exit(3)
}
err = dumpApi(basePath, api)
if err != nil {
log.Printf("Failed dumping yaml: %s", err)
os.Exit(3)
}
err = dumpPython(basePath, swagger.Info.Title, swagger.Info.Version, pythonfunctions)
if err != nil {
log.Printf("Failed dumping python: %s", err)
os.Exit(3)
}
}
+499
View File
@@ -0,0 +1,499 @@
package main
/*
Code used to generate apps from OpenAPI JSON data
Any function ending with GCP doesn't use local fileIO, but rather
google cloud storage, and also has a normal filesystem version of the
same code (doesn't required client as first argument).
This code is used in the backend to generate apps on the fly for users.
All new code is appended to backend/go-app/codegen.go
*/
import (
"context"
"crypto/md5"
"encoding/hex"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strings"
"cloud.google.com/go/storage"
"github.com/getkin/kin-openapi/openapi3"
"gopkg.in/yaml.v2"
)
var bucketName = "shuffler.appspot.com"
type WorkflowApp struct {
Name string `json:"name" yaml:"name" required:true datastore:"name"`
IsValid bool `json:"is_valid" yaml:"is_valid" required:true datastore:"is_valid"`
ID string `json:"id" yaml:"id,omitempty" required:false datastore:"id"`
Link string `json:"link" yaml:"link" required:false datastore:"link,noindex"`
AppVersion string `json:"app_version" yaml:"app_version" required:true datastore:"app_version"`
Description string `json:"description" datastore:"description" required:false yaml:"description"`
Environment string `json:"environment" datastore:"environment" required:true yaml:"environment"`
SmallImage string `json:"small_image" datastore:"small_image,noindex" required:false yaml:"small_image"`
LargeImage string `json:"large_image" datastore:"large_image,noindex" yaml:"large_image" required:false`
ContactInfo struct {
Name string `json:"name" datastore:"name" yaml:"name"`
Url string `json:"url" datastore:"url" yaml:"url"`
} `json:"contact_info" datastore:"contact_info" yaml:"contact_info" required:false`
Actions []WorkflowAppAction `json:"actions" yaml:"actions" required:true datastore:"actions"`
Authentication Authentication `json:"authentication" yaml:"authentication" required:false datastore:"authentication"`
}
type AuthenticationParams struct {
Description string `json:"description" datastore:"description" yaml:"description"`
ID string `json:"id" datastore:"id" yaml:"id"`
Name string `json:"name" datastore:"name" yaml:"name"`
Example string `json:"example" datastore:"example" yaml:"example"s`
Value string `json:"value,omitempty" datastore:"value" yaml:"value"`
Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"`
Required bool `json:"required" datastore:"required" yaml:"required"`
}
type Authentication struct {
Required bool `json:"required" datastore:"required" yaml:"required" `
Parameters []AuthenticationParams `json:"parameters" datastore:"parameters" yaml:"parameters"`
}
type AuthenticationStore struct {
Key string `json:"key" datastore:"key"`
Value string `json:"value" datastore:"value"`
}
type WorkflowAppActionParameter struct {
Description string `json:"description" datastore:"description" yaml:"description"`
ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
Name string `json:"name" datastore:"name" yaml:"name"`
Example string `json:"example" datastore:"example" yaml:"example"`
Value string `json:"value" datastore:"value" yaml:"value,omitempty"`
Multiline bool `json:"multiline" datastore:"multiline" yaml:"multiline"`
ActionField string `json:"action_field" datastore:"action_field" yaml:"actionfield,omitempty"`
Variant string `json:"variant" datastore:"variant" yaml:"variant,omitempty"`
Required bool `json:"required" datastore:"required" yaml:"required"`
Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"`
}
type SchemaDefinition struct {
Type string `json:"type" datastore:"type"`
}
type WorkflowAppAction struct {
Description string `json:"description" datastore:"description"`
ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
Name string `json:"name" datastore:"name"`
NodeType string `json:"node_type" datastore:"node_type"`
Environment string `json:"environment" datastore:"environment"`
Authentication []AuthenticationStore `json:"authentication" datastore:"authentication" yaml:"authentication,omitempty"`
Parameters []WorkflowAppActionParameter `json:"parameters" datastore: "parameters"`
Returns struct {
Description string `json:"description" datastore:"returns" yaml:"description,omitempty"`
ID string `json:"id" datastore:"id" yaml:"id,omitempty"`
Schema SchemaDefinition `json:"schema" datastore:"schema" yaml:"schema"`
} `json:"returns" datastore:"returns"`
}
func copyFile(fromfile, tofile string) error {
from, err := os.Open(fromfile)
if err != nil {
return err
}
defer from.Close()
to, err := os.OpenFile(tofile, os.O_RDWR|os.O_CREATE, 0666)
if err != nil {
return err
}
defer to.Close()
_, err = io.Copy(to, from)
if err != nil {
return err
}
return nil
}
func buildStructureGCP(client *storage.Client, swagger *openapi3.Swagger, curHash string) (string, error) {
ctx := context.Background()
// 1. Have baseline in bucket/generated_apps/baseline
// 2. Copy the baseline to a new folder with identifier name
basePath := "generated_apps"
identifier := fmt.Sprintf("%s-%s", swagger.Info.Title, curHash)
appPath := fmt.Sprintf("%s/%s", basePath, identifier)
fileNames := []string{"Dockerfile", "requirements.txt"}
for _, file := range fileNames {
src := client.Bucket(bucketName).Object(fmt.Sprintf("%s/baseline/%s", basePath, file))
dst := client.Bucket(bucketName).Object(fmt.Sprintf("%s/%s", appPath, file))
if _, err := dst.CopierFrom(src).Run(ctx); err != nil {
return "", err
}
}
return appPath, nil
}
// Builds the base structure for the app that we're making
// Returns error if anything goes wrong. This has to work if
// the python code is supposed to be generated
func buildStructure(swagger *openapi3.Swagger, curHash string) (string, error) {
//log.Printf("%#v", swagger)
// adding md5 based on input data to not overwrite earlier data.
generatedPath := "generated"
identifier := fmt.Sprintf("%s-%s", swagger.Info.Title, curHash)
appPath := fmt.Sprintf("%s/%s", generatedPath, identifier)
os.MkdirAll(appPath, os.ModePerm)
os.Mkdir(fmt.Sprintf("%s/src", appPath), os.ModePerm)
err := copyFile("baseline/Dockerfile", fmt.Sprintf("%s/%s", appPath, "Dockerfile"))
if err != nil {
log.Println("Failed to move Dockerfile")
return appPath, err
}
err = copyFile("baseline/requirements.txt", fmt.Sprintf("%s/%s", appPath, "requirements.txt"))
if err != nil {
log.Println("Failed to move requrements.txt")
return appPath, err
}
return appPath, nil
}
func makePythoncode(name, url, method string, parameters, optionalQueries []string) string {
method = strings.ToLower(method)
queryString := ""
queryData := ""
// FIXME - this might break - need to check if ? or & should be set as query
parameterData := ""
if len(optionalQueries) > 0 {
queryString += ", "
for _, query := range optionalQueries {
queryString += fmt.Sprintf("%s=\"\"", query)
queryData += fmt.Sprintf(`
if %s:
url += f"&%s={%s}"`, query, query, query)
}
}
if len(parameters) > 0 {
parameterData = fmt.Sprintf(", %s", strings.Join(parameters, ", "))
}
// FIXME - add checks for query data etc
data := fmt.Sprintf(` async def %s_%s(self%s%s):
url=f"%s"
%s
return requests.%s(url).text
`, name, method, parameterData, queryString, url, queryData, method)
return data
}
func generateYaml(swagger *openapi3.Swagger) (WorkflowApp, []string, error) {
api := WorkflowApp{}
log.Printf("%#v", swagger.Info)
if len(swagger.Info.Title) == 0 {
return WorkflowApp{}, []string{}, errors.New("Swagger.Info.Title can't be empty.")
}
if len(swagger.Servers) == 0 {
return WorkflowApp{}, []string{}, errors.New("Swagger.Servers can't be empty. Add 'servers':[{'url':'hostname.com'}'")
}
api.Name = swagger.Info.Title
api.Description = swagger.Info.Description
api.IsValid = true
api.Link = swagger.Servers[0].URL // host doesnt exist lol
api.AppVersion = "1.0.0"
api.Environment = "cloud"
api.ID = ""
api.SmallImage = ""
api.LargeImage = ""
// This is the python code to be generated
// Could just as well be go at this point lol
pythonFunctions := []string{}
for actualPath, path := range swagger.Paths {
//log.Printf("%#v", path)
//log.Printf("%#v", actualPath)
// Find the path name and add it to makeCode() param
firstQuery := true
if path.Get != nil {
// What to do with this, hmm
functionName := strings.ReplaceAll(path.Get.Summary, " ", "_")
functionName = strings.ToLower(functionName)
action := WorkflowAppAction{
Description: path.Get.Description,
Name: path.Get.Summary,
NodeType: "action",
Environment: api.Environment,
Parameters: []WorkflowAppActionParameter{},
}
action.Returns.Schema.Type = "string"
baseUrl := fmt.Sprintf("%s%s", api.Link, actualPath)
//log.Println(path.Parameters)
// Parameters: []WorkflowAppActionParameter{},
// FIXME - add data for POST stuff
firstQuery = true
optionalQueries := []string{}
parameters := []string{}
optionalParameters := []WorkflowAppActionParameter{}
if len(path.Get.Parameters) > 0 {
for _, param := range path.Get.Parameters {
curParam := WorkflowAppActionParameter{
Name: param.Value.Name,
Description: param.Value.Description,
Multiline: false,
Required: param.Value.Required,
Schema: SchemaDefinition{
Type: param.Value.Schema.Value.Type,
},
}
if param.Value.Required {
action.Parameters = append(action.Parameters, curParam)
} else {
optionalParameters = append(optionalParameters, curParam)
}
if param.Value.In == "path" {
log.Printf("PATH!: %s", param.Value.Name)
parameters = append(parameters, param.Value.Name)
//baseUrl = fmt.Sprintf("%s%s", baseUrl)
} else if param.Value.In == "query" {
log.Printf("QUERY!: %s", param.Value.Name)
if !param.Value.Required {
optionalQueries = append(optionalQueries, param.Value.Name)
continue
}
parameters = append(parameters, param.Value.Name)
if firstQuery {
baseUrl = fmt.Sprintf("%s?%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
firstQuery = false
} else {
baseUrl = fmt.Sprintf("%s&%s={%s}", baseUrl, param.Value.Name, param.Value.Name)
firstQuery = false
}
}
}
}
// ensuring that they end up last in the specification
// (order is ish important for optional params) - they need to be last.
for _, optionalParam := range optionalParameters {
action.Parameters = append(action.Parameters, optionalParam)
}
curCode := makePythoncode(functionName, baseUrl, "get", parameters, optionalQueries)
pythonFunctions = append(pythonFunctions, curCode)
api.Actions = append(api.Actions, action)
}
}
return api, pythonFunctions, nil
}
func verifyApi(api WorkflowApp) WorkflowApp {
if api.AppVersion == "" {
api.AppVersion = "1.0.0"
}
return api
}
func dumpPythonGCP(client *storage.Client, basePath, name, version string, pythonFunctions []string) error {
//log.Printf("%#v", api)
log.Printf(strings.Join(pythonFunctions, "\n"))
parsedCode := fmt.Sprintf(`import requests
import asyncio
import json
from walkoff_app_sdk.app_base import AppBase
class %s(AppBase):
"""
Autogenerated class by Shuffler
"""
__version__ = "%s"
app_name = "%s"
def __init__(self, redis, logger, console_logger=None):
self.verify = False
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
super().__init__(redis, logger, console_logger)
%s
if __name__ == "__main__":
asyncio.run(CarbonBlack.run(), debug=True)
`, name, version, name, strings.Join(pythonFunctions, "\n"))
// Create bucket handle
ctx := context.Background()
bucket := client.Bucket(bucketName)
obj := bucket.Object(fmt.Sprintf("%s/src/app.py", basePath))
w := obj.NewWriter(ctx)
if _, err := fmt.Fprintf(w, parsedCode); err != nil {
return err
}
// Close, just like writing a file.
if err := w.Close(); err != nil {
return err
}
return nil
}
func dumpPython(basePath, name, version string, pythonFunctions []string) error {
//log.Printf("%#v", api)
log.Printf(strings.Join(pythonFunctions, "\n"))
parsedCode := fmt.Sprintf(`import requests
import asyncio
import json
from walkoff_app_sdk.app_base import AppBase
class %s(AppBase):
"""
Autogenerated class by Shuffler
"""
__version__ = "%s"
app_name = "%s"
def __init__(self, redis, logger, console_logger=None):
self.verify = False
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
super().__init__(redis, logger, console_logger)
%s
if __name__ == "__main__":
asyncio.run(CarbonBlack.run(), debug=True)
`, name, version, name, strings.Join(pythonFunctions, "\n"))
err := ioutil.WriteFile(fmt.Sprintf("%s/src/app.py", basePath), []byte(parsedCode), os.ModePerm)
if err != nil {
return err
}
fmt.Println(parsedCode)
//log.Println(string(data))
return nil
}
func dumpApiGCP(client *storage.Client, basePath string, api WorkflowApp) error {
//log.Printf("%#v", api)
data, err := yaml.Marshal(api)
if err != nil {
log.Printf("Error with yaml marshal: %s", err)
return err
}
// Create bucket handle
ctx := context.Background()
bucket := client.Bucket(bucketName)
obj := bucket.Object(fmt.Sprintf("%s/app.yaml", basePath))
w := obj.NewWriter(ctx)
if _, err := fmt.Fprintf(w, string(data)); err != nil {
return err
}
// Close, just like writing a file.
if err := w.Close(); err != nil {
return err
}
//log.Println(string(data))
return nil
}
func dumpApi(basePath string, api WorkflowApp) error {
//log.Printf("%#v", api)
data, err := yaml.Marshal(api)
if err != nil {
log.Printf("Error with yaml marshal: %s", err)
return err
}
err = ioutil.WriteFile(fmt.Sprintf("%s/api.yaml", basePath), []byte(data), os.ModePerm)
if err != nil {
return err
}
//log.Println(string(data))
return nil
}
func main() {
data := []byte(`{"swagger":"3.0","info":{"title":"hi","description":"you","version":"1.0"},"servers":[{"url":"https://shuffler.io/api/v1"}],"host":"shuffler.io","basePath":"/api/v1","schemes":["https:"],"paths":{"/workflows":{"get":{"responses":{"default":{"description":"default","schema":{}}},"summary":"Get workflows","description":"Get workflows","parameters":[]}},"/workflows/{id}":{"get":{"responses":{"default":{"description":"default","schema":{}}},"summary":"Get workflow","description":"Get workflow","parameters":[{"in":"query","name":"forgetme","description":"Generated by shuffler.io OpenAPI","required":true,"schema":{"type":"string"}},{"in":"query","name":"anotherone","description":"Generated by shuffler.io OpenAPI","required":false,"schema":{"type":"string"}},{"in":"query","name":"hi","description":"Generated by shuffler.io OpenAPI","required":true,"schema":{"type":"string"}},{"in":"path","name":"id","description":"Generated by shuffler.io OpenAPI","required":true,"schema":{"type":"string"}}]}}},"securityDefinitions":{}}`)
ctx := context.Background()
client, err := storage.NewClient(ctx)
if err != nil {
log.Printf("Failed to create client: %v", err)
os.Exit(3)
}
hasher := md5.New()
hasher.Write(data)
newmd5 := hex.EncodeToString(hasher.Sum(nil))
swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData(data)
if err != nil {
log.Printf("Swagger validation error: %s", err)
os.Exit(3)
}
if strings.Contains(swagger.Info.Title, " ") {
strings.ReplaceAll(swagger.Info.Title, " ", "")
}
basePath, err := buildStructureGCP(client, swagger, newmd5)
if err != nil {
log.Printf("Failed to build base structure: %s", err)
os.Exit(3)
}
api, pythonfunctions, err := generateYaml(swagger)
if err != nil {
log.Printf("Failed building and generating yaml: %s", err)
os.Exit(3)
}
err = dumpApiGCP(client, basePath, api)
if err != nil {
log.Printf("Failed dumping yaml: %s", err)
os.Exit(3)
}
err = dumpPythonGCP(client, basePath, swagger.Info.Title, swagger.Info.Version, pythonfunctions)
if err != nil {
log.Printf("Failed dumping python: %s", err)
os.Exit(3)
}
}
+5
View File
@@ -0,0 +1,5 @@
# Generators
This folder contains an attempt at creating apps & similar from python libraries
## Howto
+26
View File
@@ -0,0 +1,26 @@
# Base our app image off of the WALKOFF App SDK image
FROM frikky/shuffle:app_sdk as base
# We're going to stage away all of the bloat from the build tools so lets create a builder stage
FROM base as builder
# Install all alpine build tools needed for our pip installs
RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev
# Install all of our pip packages in a single directory that we can copy to our base image later
RUN mkdir /install
WORKDIR /install
COPY requirements.txt /requirements.txt
RUN pip install --prefix="/install" -r /requirements.txt
# Switch back to our base image and copy in all of our built packages and source code
FROM base
COPY --from=builder /install /usr/local
COPY src /app
# Install any binary dependencies needed in our final image - this can be a lot of different stuff
RUN apk --no-cache add --update libmagic
# Finally, lets run our app!
WORKDIR /app
CMD python app.py --log-level DEBUG
@@ -0,0 +1,14 @@
version: '3.4'
services:
thehive4py:
build:
context: .
dockerfile: Dockerfile
env_file:
- env.txt
restart: "no"
deploy:
mode: replicated
replicas: 10
restart_policy:
condition: none
+4
View File
@@ -0,0 +1,4 @@
REDIS_URI=redis://redis
REDIS_ACTION_RESULT_CH=action-results
REDIS_ACTION_RESULTS_GROUP=action-results-group
APP_NAME=
@@ -0,0 +1 @@
# No extra requirements needed
+525
View File
@@ -0,0 +1,525 @@
# Read a directory
# Find python functions
# Generate yaml
# FIXME:
# Position, default_value and function in params
# TO ADD:
# from walkoff_app_sdk.app_base import AppBase
# class TheHive(AppBase): <-- Add appbase
# __version__ = version within class
# app_name = app_name in class
# if __name__ == "__main__":
# asyncio.run(TheHive.run(), debug=True) <-- APPEND SHIT HERE
# async infront of every function?
# Add async library to imports
# Make wrapper class? <-- within app.py
# 1. Generate app.yaml (functions with returns etc)
# 2. Generate app.py (with imports to the original function etc
# 3. Build requirements.txt based on the items necessary
# 4. Check whether it runs?
import os
import yaml
import jedi
import shutil
# Testing generator
entrypoint_directory = "thehive4py"
include_requirements = False
if not os.path.exists(entrypoint_directory):
include_requirements = True
print("Requires library in requirements")
source = '''
import %s
%s.
''' % (entrypoint_directory, entrypoint_directory)
splitsource = source.split("\n")
# Find modules AKA files
def get_modules():
curline = splitsource[-2]
print(splitsource, curline)
entrypoint = jedi.Script(source, line=len(splitsource)-1, column=len(curline))
modules = []
completions = entrypoint.completions()
for item in completions:
if item.type != "module":
continue
modules.append(item.name)
return modules
def loop_modules(modules, data):
# Loop modules AKA files - this is garbage but works lmao
for module in modules:
modulesplit = list(splitsource)
modulesplit[2] = "%s%s." % (modulesplit[2], module)
#print(modulesplit)
source = "\n".join(modulesplit)
entrypoint = jedi.Script(source, line=len(modulesplit)-1, column=len(modulesplit[2]))
# Loop classes in the files
for classcompletion in entrypoint.completions():
if classcompletion.type != "class":
continue
if not classcompletion.full_name.startswith(modulesplit[2]):
continue
# Same thing again, but for functions within classes
# CBA with subclasses etc atm
#print(classcompletion.full_name, modulesplit[2])
classplit = list(modulesplit)
classplit[2] = "%s." % (classcompletion.full_name)
#print(modulesplit)
source = "\n".join(classplit)
entrypoint = jedi.Script(source, line=len(classplit)-1, column=len(classplit[2]))
# List of functions sorted by their name
nameinternalfunctions = []
for functioncompletion in entrypoint.completions():
if functioncompletion.type != "function":
continue
if not functioncompletion.full_name.startswith(classplit[2]):
continue
nameinternalfunctions.append(functioncompletion)
#print(nameinternalfunctions)
# List of functions sorted by their line in the file (reversed)
# CODE USED TO ACTUALLY PRINT THE CODE
#prevnumber = 0
#numberinternalfunctions = sorted(nameinternalfunctions, key=lambda k: k.line, reverse=True)
numberinternalfunctions = sorted(nameinternalfunctions, key=lambda k: k.line)
prevnumber = 0
origparent = "TheHiveApi"
# Predefined functions? - maybe skip: __init__
skip_functions = ["__init__"]
skip_parameters = [""]
cnt = 0
for item in numberinternalfunctions:
if item.parent().name != origparent:
continue
# FIXME - prolly wrong
if item.name in skip_functions or (item.name.startswith("__") and item.name.endswith("__")):
continue
# FIXME - remove
#print(item.get_line_code())
#if "=" not in item.get_line_code():
# continue
#if item.docstring() in item.get_line_code():
# print("NO DOCSTRING FOR: %s. Skipping!" % item.name)
# cnt += 1
# continue
curfunction = {
"name": item.name,
"description": "HEY",
}
params = []
curreturn = {}
function = item.docstring().split("\n")[0]
for line in item.docstring().split("\n"):
if not line:
continue
linesplit = line.split(" ")
try:
curname = linesplit[1][:-1]
except IndexError as e:
print("IndexError: %s. Line: %s" % (e, line))
continue
paramfound = False
foundindex = 0
cnt = 0
for param in params:
#print(param["name"], curname)
if param["name"] == curname:
#print("ALREADY EXISTS: %s" % curname)
paramfound = True
foundindex = cnt
break
cnt += 1
# CBA finding a good parser, as that seemed impossible :(
# Skipped :return
if line.startswith(":param"):
if not paramfound:
#print("HERE!: %s" % line)
curparam = {}
#print(line)
curparam["name"] = curname
curparam["description"] = " ".join(linesplit[2:])
#print(curparam["description"])
if "\r\n" in curparam["description"]:
curparam["description"] = " ".join(curparam["description"].split("\r\n"))
if "\n" in curparam["description"]:
curparam["description"] = " ".join(curparam["description"].split("\n"))
curparam["function"] = function
#curparam["docstring"] = item.docstring()
params.append(curparam)
elif line.startswith(":type"):
if paramfound:
params[foundindex]["schema"] = {}
params[foundindex]["schema"]["type"] = " ".join(linesplit[2:])
#print(params)
#print(line)
elif line.startswith(":rtype"):
curreturn["type"] = " ".join(linesplit[1:])
# Check whether param is required
# FIXME - remove
#if len(params) != 0:
# print(params)
# continue
#print(function)
#print(params)
# FIXME - this might crash when missing docstrings
# FIXME - is also bad splitt (can be written without e.g. spaces
# This should maybe be done first? idk
fields = function.split("(")[1][:-1].split(", ")
if len(params) == 0:
# Handle missing docstrings
params = []
for item in fields:
params.append({
"name": item,
"description": "",
"schema": {},
"function": function,
})
cnt = 0
for param in params:
found = False
for field in fields:
if param["name"] in field:
if "=" in field:
param["required"] = False
param["default_value"] = field
else:
param["required"] = True
found = True
break
if not param.get("schema"):
#print("Defining object schema for %s" % param["name"])
param["schema"] = {}
param["schema"]["type"] = "object"
param["position"] = cnt
if not found:
# FIXME - waht here?
pass
#print("HANDLE NOT FOUND")
#print(param)
#print(fields)
cnt += 1
if len(params) > 0:
curfunction["parameters"] = params
if not curfunction.get("returns"):
curfunction["returns"] = {}
curfunction["returns"]["schema"] = {}
curfunction["returns"]["schema"]["type"] = "object"
#print(curfunction)
try:
print("Finished prepping %s with %d parameters and return %s" % (item.name, len(curfunction["parameters"]), curfunction["returns"]["schema"]["type"]))
except KeyError as e:
print("Error: %s" % e)
#print("Finished prepping %s with 0 parameters and return %s" % (item.name, curfunction["returns"]["schema"]["type"]))
curfunction["parameters"] = []
except AttributeError as e:
pass
try:
data["actions"].append(curfunction)
except KeyError:
data["actions"] = []
data["actions"].append(curfunction)
#return data
# FIXME
#if cnt == breakcnt:
# break
#cnt += 1
# Check if
# THIS IS TO GET READ THE ACTUAL CODE
#functioncode = item.get_line_code(after=prevnumber-item.line-1)
#prevnumber = item.line
# break
return data
# Generates the base information necessary to make an api.yaml file
def generate_base_yaml(filename, version, appname):
print("Generating base app for library %s with version %s" % (appname, version))
data = {
"walkoff_version": "0.0.1",
"app_version": version,
"name": appname,
"description": "Autogenerated yaml with @Frikkylikeme's generator",
"contact_info": {
"name": "@frikkylikeme",
"url": "https://github.com/frikky",
}
}
return data
def generate_app(filepath, data):
tbd = [
"library_path",
"import_class",
"required_init"
]
# FIXME - add to data dynamically and remove
data["library_path"] = "thehive4py.api"
data["import_class"] = "TheHiveApi"
data["required_init"] = {"url": "http://localhost:9000", "principal": "asd"}
wrapperstring = ""
cnt = 0
# FIXME - only works for strings currently
for key, value in data["required_init"].items():
if cnt != len(data["required_init"]):
wrapperstring += "%s=\"%s\", " % (key, value)
cnt += 1
wrapperstring = wrapperstring[:-2]
wrapper = "self.wrapper = %s(%s)" % (data["import_class"], wrapperstring)
name = data["name"]
if ":" in data["name"]:
name = data["name"].split(":")[0]
if not data.get("actions"):
print("No actions found for %s in path %s" % (entrypoint_directory, data["library_path"]))
print("Folder might be missing (or unexported (__init__.py), library not installed (pip) or library action missing")
exit()
functions = []
for action in data["actions"]:
internalparamstring = ""
paramstring = ""
try:
for param in action["parameters"]:
if param["required"] == False:
paramstring += "%s, " % (param["default_value"])
else:
paramstring += "%s, " % param["name"]
except KeyError:
action["parameters"] = []
#internalparamstring += "%s, " % param["name"]
paramstring = paramstring[:-2]
#internalparamstring = internalparamstring[:-2]
functionstring = ''' async def %s(%s):
return self.wrapper.%s(%s)
''' % (action["name"], paramstring, action["name"], paramstring)
functions.append(functionstring)
filedata = '''from walkoff_app_sdk.app_base import AppBase
import asyncio
from %s import %s
class %sWrapper(AppBase):
__version__ = "%s"
app_name = "%s"
def __init__(self, redis, logger, console_logger=None):
"""
Each app should have this __init__ to set up Redis and logging.
:param redis:
:param logger:
:param console_logger:
"""
super().__init__(redis, logger, console_logger)
%s
%s
if __name__ == "__main__":
asyncio.run(%sWrapper.run(), debug=True)
''' % ( \
data["library_path"],
data["import_class"],
name,
data["app_version"],
name,
wrapper,
"\n".join(functions),
name
)
# Simple key cleanup
for item in tbd:
try:
del data[item]
except KeyError:
pass
tbd_action = []
tbd_param = [
"position",
"default_value",
"function"
]
for action in data["actions"]:
for param in action["parameters"]:
for item in tbd_param:
try:
del param[item]
except KeyError:
pass
for item in tbd_action:
try:
del action[item]
except KeyError:
pass
# FIXME - add how to initialize the class
with open(filepath, "w") as tmp:
tmp.write(filedata)
return data
def dump_yaml(filename, data):
with open(filename, 'w') as outfile:
yaml.dump(data, outfile, default_flow_style=False)
def build_base_structure(appname, version):
outputdir = "generated"
app_path = "%s/%s" % (outputdir, appname)
filepath = "%s/%s" % (app_path, version)
srcdir_path = "%s/src" % (filepath)
directories = [
outputdir,
app_path,
filepath,
srcdir_path
]
for directory in directories:
try:
os.mkdir(directory)
except FileExistsError:
print("%s already exists. Skipping." % directory)
# "docker-compose.yml",
# "env.txt",
filenames = [
"Dockerfile",
"requirements.txt"
]
#if strings.
# include_requirements = False
for filename in filenames:
ret = shutil.copyfile("baseline/%s" % filename, "%s/%s" % (filepath, filename))
print("Copied baseline/%s." % filename)
def move_files(appname, version):
applocation = "../../functions/apps/%s" % appname
if not os.path.exists("../../functions/apps"):
os.mkdir("../../functions/apps")
if not os.path.exists(applocation):
os.mkdir(applocation)
versionlocation = "%s/%s" % (applocation, version)
if not os.path.exists(versionlocation):
os.mkdir(versionlocation)
shutil.rmtree(versionlocation)
shutil.move("generated/%s/%s" % (appname, version), versionlocation)
print("\nMoved files to %s" % versionlocation)
def main():
appname = entrypoint_directory
version = "0.0.1"
output_path = "generated/%s/%s" % (appname, version)
api_yaml_path = "%s/api.yaml" % (output_path)
app_python_path = "%s/src/app.py" % (output_path)
# Builds the directory structure for the app
build_base_structure(appname, version)
# Generates the yaml based on input library etc
data = generate_base_yaml(api_yaml_path, version, appname)
modules = get_modules()
data = loop_modules(modules, data)
# Generates app file
data = generate_app(app_python_path, data)
# Dumps the yaml to specified directory
dump_yaml(api_yaml_path, data)
# Move the file to functions/apps repository
move_files(appname, version)
if __name__ == "__main__":
main()
+2
View File
@@ -0,0 +1,2 @@
jedi
pyyaml
+26
View File
@@ -0,0 +1,26 @@
from golang as builder
# Add files
RUN mkdir /app
WORKDIR /app
ADD ./go-app/main.go /app
ADD ./go-app/walkoff.go /app
ADD ./go-app/docker.go /app
ADD ./go-app/codegen.go /app
ADD ./go-app/go.mod /app
RUN go get -v
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o webapp .
# Certificate build
FROM alpine:latest as certs
RUN apk --update add ca-certificates
from scratch
COPY --from=builder /app/ /
COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
EXPOSE 5001
CMD ["./webapp"]
+61
View File
@@ -0,0 +1,61 @@
# Backend setup
1 Go to https://console.cloud.google.com/apis/credentials?project=shuffle-241517&folder&organizationId and get credentials
2. Move the file to current folder (or make step 3 be your download folder or w/e)
3. export GOOGLE_APPLICATION_CREDENTIALS=$(pwd)/Shuffle-2a19ff64af66.json
# Backend run testserver (appengine)
1. Set up gcloud locally
```bash
dev_appserver.py go-app/ --port=5001 --host=0.0.0.0 --enable_host_checking=false
```
# Backend deploy
* I created a simple script that moves the data into your GOPATH and deploys for you. This will require more tests in the future.
# OpenAPI spec checks
Paths:
* /path/{variablename}?queryvar= <-- variable
* ^variablename needs to be part of parameters too.
* ^queryvar needs to be part of parameters too.
```
parameters:
- name: variablename
in: path
description: Blah blah
required: true/false
schema:
type: string
enum: [a, b, c] # <-- not necessary, but could be great
- name: queryvar
in: query
description: blah blah
required: true/false
schema:
type: string
enum: [a, b, c]
```
* requestBody? Not in GET, DELETE & HEAD. Can consume JSON, XML, form data, plai ntext & others. Can use markdown for the description.
* Do I care about the response? Maybe :o
```
requestBody:
description: Optional kind of description
required: false/true
content:
application/json:
schema:
type: object
additionalProperties: true
properties:
name:
type: string
fav_number:
type: integer
required:
- name
- email
encoding:
color:
style: form
explode: false
```
+5
View File
@@ -0,0 +1,5 @@
# 2. docker run -p 8000:8000
FROM google/cloud-sdk
EXPOSE 8000
CMD ["gcloud", "beta", "emulators", "datastore", "start", "--project=shuffle", "--host-port", "0.0.0.0:8000", "--data-dir=/etc/shuffle"]
+8
View File
@@ -0,0 +1,8 @@
# Deploys to backend
echo "Deploying to appengine."
mkdir -p $GOPATH/src/github.com/frikky/shuffle
cp -r go-app/* $GOPATH/src/github.com/frikky/shuffle
cd $GOPATH/src/github.com/frikky/shuffle
go build
go test
gcloud app deploy $GOPATH/src/github.com/frikky/shuffle/app.yaml
+6
View File
@@ -0,0 +1,6 @@
## RUN
```bash
cd shaffuru/
dev_appserver.py --port=5001 --host=0.0.0.0 --enable_host_checking=false .
```
+98
View File
@@ -0,0 +1,98 @@
runtime: go111
env_variables:
automatic_scaling:
max_instances: 1
min_instances: 1
handlers:
- url: /api/(.*)
script: auto
secure: always
- url: /static/js/(.*)
static_files: build/static/js/\1
upload: build/static/js/(.*)
secure: always
- url: /static/css/(.*)
static_files: build/static/css/\1
upload: build/static/css/(.*)
secure: always
- url: /images/(.*)
static_files: build/images/\1
upload: build/images/(.*)
secure: always
- url: /(.*\.(json|ico))$
static_files: build/\1
upload: build/.*\.(json|ico)$
secure: always
- url: /manifest.json
static_files: build/manifest.json
upload: build/manifest.json
secure: always
# lol.. wildcard doesn't work with /api/(.*) for some reason
- url: /
static_files: build/index.html
upload: build/index.html
secure: always
- url: /home
static_files: build/index.html
upload: build/index.html
secure: always
- url: /passwordreset
static_files: build/index.html
upload: build/index.html
secure: always
- url: /login
static_files: build/index.html
upload: build/index.html
secure: always
- url: /register
static_files: build/index.html
upload: build/index.html
secure: always
- url: /workflows
static_files: build/index.html
upload: build/index.html
secure: always
- url: /workflows/(.*)
static_files: build/index.html
upload: build/index.html
secure: always
- url: /info/(.*)
static_files: build/index.html
upload: build/index.html
secure: always
- url: /docs/(.*)
static_files: build/index.html
upload: build/index.html
secure: always
- url: /docs
static_files: build/index.html
upload: build/index.html
secure: always
- url: /settings
static_files: build/index.html
upload: build/index.html
secure: always
- url: /apps
static_files: build/index.html
upload: build/index.html
secure: always
- url: /contact
static_files: build/index.html
upload: build/index.html
secure: always
- url: /apps/(.*)
static_files: build/index.html
upload: build/index.html
secure: always
- url: /register/(.*)
static_files: build/index.html
upload: build/index.html
secure: always
- url: /passwordreset/(.*)
static_files: build/index.html
upload: build/index.html
secure: always
File diff suppressed because it is too large Load Diff
+644
View File
@@ -0,0 +1,644 @@
package main
// Docker
import (
"archive/tar"
"path/filepath"
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
network "github.com/docker/docker/api/types/network"
"github.com/docker/docker/client"
natting "github.com/docker/go-connections/nat"
"github.com/go-git/go-billy/v5"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
//"google.golang.org/appengine"
)
// Parses a directory with a Dockerfile into a tar for Docker images..
func getParsedTar(tw *tar.Writer, baseDir, extra string) error {
return filepath.Walk(baseDir, func(file string, fi os.FileInfo, err error) error {
if file == baseDir {
return nil
}
//log.Printf("File: %s", file)
//log.Printf("Fileinfo: %#v", fi)
switch mode := fi.Mode(); {
case mode.IsDir():
// do directory recursion
//log.Printf("DIR: %s", file)
// Append "src" as extra here
filenamesplit := strings.Split(file, "/")
filename := fmt.Sprintf("%s%s/", extra, filenamesplit[len(filenamesplit)-1])
tmpExtra := fmt.Sprintf(filename)
//log.Printf("TmpExtra: %s", tmpExtra)
err = getParsedTar(tw, file, tmpExtra)
if err != nil {
log.Printf("Directory parse issue: %s", err)
return err
}
case mode.IsRegular():
// do file stuff
//log.Printf("FILE: %s", file)
fileReader, err := os.Open(file)
if err != nil {
return err
}
// Read the actual Dockerfile
readFile, err := ioutil.ReadAll(fileReader)
if err != nil {
log.Printf("Not file: %s", err)
return err
}
filenamesplit := strings.Split(file, "/")
filename := fmt.Sprintf("%s%s", extra, filenamesplit[len(filenamesplit)-1])
//log.Printf("Filename: %s", filename)
tarHeader := &tar.Header{
Name: filename,
Size: int64(len(readFile)),
}
//Writes the header described for the TAR file
err = tw.WriteHeader(tarHeader)
if err != nil {
return err
}
// Writes the dockerfile data to the TAR file
_, err = tw.Write(readFile)
if err != nil {
return err
}
}
return nil
})
}
// Custom TAR builder in memory for Docker images
func getParsedTarMemory(fs billy.Filesystem, tw *tar.Writer, baseDir, extra string) error {
// This one has to use baseDir + Extra
newBase := fmt.Sprintf("%s%s", baseDir, extra)
dir, err := fs.ReadDir(newBase)
if err != nil {
return err
}
for _, file := range dir {
// Folder?
switch mode := file.Mode(); {
case mode.IsDir():
filename := file.Name()
filenamesplit := strings.Split(filename, "/")
tmpExtra := fmt.Sprintf("%s%s/", extra, filenamesplit[len(filenamesplit)-1])
//log.Printf("EXTRA: %s", tmpExtra)
err = getParsedTarMemory(fs, tw, baseDir, tmpExtra)
if err != nil {
log.Printf("Directory parse issue: %s", err)
return err
}
case mode.IsRegular():
filenamesplit := strings.Split(file.Name(), "/")
filename := fmt.Sprintf("%s%s", extra, filenamesplit[len(filenamesplit)-1])
// Newbase
path := fmt.Sprintf("%s%s", newBase, file.Name())
fileReader, err := fs.Open(path)
if err != nil {
return err
}
readFile, err := ioutil.ReadAll(fileReader)
if err != nil {
log.Printf("Not file: %s", err)
return err
}
//log.Printf("Filename: %s", filename)
// FIXME - might need the folder from EXTRA here
// Name has to be e.g. just "requirements.txt"
tarHeader := &tar.Header{
Name: filename,
Size: int64(len(readFile)),
}
//Writes the header described for the TAR file
err = tw.WriteHeader(tarHeader)
if err != nil {
return err
}
// Writes the dockerfile data to the TAR file
_, err = tw.Write(readFile)
if err != nil {
return err
}
}
}
return nil
}
// Custom Docker image builder wrapper in memory
func buildImageMemory(fs billy.Filesystem, tags []string, dockerfileFolder string) error {
ctx := context.Background()
client, err := client.NewEnvClient()
if err != nil {
log.Printf("Unable to create docker client: %s", err)
return err
}
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
defer tw.Close()
log.Printf("Setting up memory build structure for folder: %s", dockerfileFolder)
err = getParsedTarMemory(fs, tw, dockerfileFolder, "")
if err != nil {
log.Printf("Tar issue: %s", err)
return err
}
dockerFileTarReader := bytes.NewReader(buf.Bytes())
// Dockerfile is inside the TAR itself. Not local context
buildOptions := types.ImageBuildOptions{
Remove: true,
Tags: tags,
}
// Build the actual image
imageBuildResponse, err := client.ImageBuild(
ctx,
dockerFileTarReader,
buildOptions,
)
log.Printf("IMAGERESPONSE: %#v", imageBuildResponse.Body)
if err != nil {
return err
}
// Read the STDOUT from the build process
defer imageBuildResponse.Body.Close()
_, err = io.Copy(os.Stdout, imageBuildResponse.Body)
if err != nil {
return err
}
return nil
}
func buildImage(tags []string, dockerfileFolder string) error {
ctx := context.Background()
client, err := client.NewEnvClient()
if err != nil {
log.Printf("Unable to create docker client: %s", err)
return err
}
log.Printf("Tags: %s", tags)
dockerfileSplit := strings.Split(dockerfileFolder, "/")
// Create a buffer
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
defer tw.Close()
baseDir := strings.Join(dockerfileSplit[0:len(dockerfileSplit)-1], "/")
// Builds the entire folder into buf
err = getParsedTar(tw, baseDir, "")
if err != nil {
log.Printf("Tar issue: %s", err)
}
dockerFileTarReader := bytes.NewReader(buf.Bytes())
buildOptions := types.ImageBuildOptions{
Remove: true,
Tags: tags,
}
// Build the actual image
imageBuildResponse, err := client.ImageBuild(
ctx,
dockerFileTarReader,
buildOptions,
)
if err != nil {
return err
}
// Read the STDOUT from the build process
defer imageBuildResponse.Body.Close()
_, err = io.Copy(os.Stdout, imageBuildResponse.Body)
if err != nil {
return err
}
return nil
}
// FIXME - very specific for webhooks. Make it easier?
func stopWebhook(image string, identifier string) error {
ctx := context.Background()
containername := fmt.Sprintf("%s-%s", image, identifier)
cli, err := client.NewEnvClient()
if err != nil {
log.Println("Unable to create docker client")
return err
}
// containers, err := cli.ContainerList(ctx, types.ContainerListOptions{
// All: true,
// })
if err := cli.ContainerStop(ctx, containername, nil); err != nil {
log.Printf("Unable to stop container %s - running removal anyway, just in case: %s", containername, err)
}
removeOptions := types.ContainerRemoveOptions{
RemoveVolumes: true,
Force: true,
}
if err := cli.ContainerRemove(ctx, containername, removeOptions); err != nil {
log.Printf("Unable to remove container: %s", err)
}
return nil
}
// FIXME - remember to set DOCKER_API_VERSION
// FIXME - remove github.com/docker/docker/vendor
// FIXME - Library dependencies for NAT is fucked..
// https://docs.docker.com/develop/sdk/examples/
func deployWebhook(image string, identifier string, path string, port string, callbackurl string, apikey string) error {
cli, err := client.NewEnvClient()
if err != nil {
fmt.Println("Unable to create docker client")
return err
}
newport, err := natting.NewPort("tcp", port)
if err != nil {
fmt.Println("Unable to create docker port")
return err
}
// FIXME - logging?
hostConfig := &container.HostConfig{
PortBindings: natting.PortMap{
newport: []natting.PortBinding{
{
HostIP: "0.0.0.0",
HostPort: port,
},
},
},
RestartPolicy: container.RestartPolicy{
Name: "always",
},
LogConfig: container.LogConfig{
Type: "json-file",
Config: map[string]string{},
},
}
//networkConfig := &network.NetworkSettings{}
networkConfig := &network.NetworkingConfig{
EndpointsConfig: map[string]*network.EndpointSettings{},
}
test := &network.EndpointSettings{
Gateway: "helo",
}
networkConfig.EndpointsConfig["bridge"] = test
exposedPorts := map[natting.Port]struct{}{
newport: struct{}{},
}
config := &container.Config{
Image: image,
Env: []string{
fmt.Sprintf("URIPATH=%s", path),
fmt.Sprintf("HOOKPORT=%s", port),
fmt.Sprintf("CALLBACKURL=%s", callbackurl),
fmt.Sprintf("APIKEY=%s", apikey),
fmt.Sprintf("HOOKID=%s", identifier),
},
ExposedPorts: exposedPorts,
Hostname: fmt.Sprintf("%s-%s", image, identifier),
}
cont, err := cli.ContainerCreate(
context.Background(),
config,
hostConfig,
networkConfig,
fmt.Sprintf("%s-%s", image, identifier),
)
if err != nil {
log.Println(err)
return err
}
cli.ContainerStart(context.Background(), cont.ID, types.ContainerStartOptions{})
log.Printf("Container %s is created", cont.ID)
return nil
}
// Starts a new webhook
func handleStopHookDocker(resp http.ResponseWriter, request *http.Request) {
cors := handleCors(resp, request)
if cors {
return
}
location := strings.Split(request.URL.String(), "/")
var fileId string
if location[1] == "api" {
if len(location) <= 4 {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
fileId = location[4]
}
if len(fileId) != 32 {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "message": "ID not valid"}`))
return
}
ctx := context.Background()
hook, err := getHook(ctx, fileId)
if err != nil {
log.Printf("Failed getting hook: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
log.Printf("Status: %s", hook.Status)
log.Printf("Running: %t", hook.Running)
if !hook.Running {
message := fmt.Sprintf("Error: %s isn't running", hook.Id)
log.Println(message)
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "%s"}`, message)))
return
}
hook.Status = "stopped"
hook.Running = false
hook.Actions = []HookAction{}
err = setHook(ctx, *hook)
if err != nil {
log.Printf("Failed setting hook: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
image := "webhook"
// This is here to force stop and remove the old webhook
err = stopWebhook(image, fileId)
if err != nil {
log.Printf("Container stop issue for %s-%s: %s", image, fileId, err)
}
resp.WriteHeader(200)
resp.Write([]byte(`{"success": true, "message": "Stopped webhook"}`))
}
// THis is an example
// Can also be used as base data?
var webhook = `{
"id": "d6ef8912e8bd37776e654cbc14c2629c",
"info": {
"url": "http://localhost:5001",
"name": "TheHive",
"description": "Webhook for TheHive"
},
"transforms": {},
"actions": {},
"type": "webhook",
"running": false,
"status": "stopped"
}`
// Starts a new webhook
func handleDeleteHookDocker(resp http.ResponseWriter, request *http.Request) {
ctx := context.Background()
cors := handleCors(resp, request)
if cors {
return
}
location := strings.Split(request.URL.String(), "/")
var fileId string
if location[1] == "api" {
if len(location) <= 4 {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
fileId = location[4]
}
if len(fileId) != 32 {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "message": "ID not valid"}`))
return
}
err := DeleteKey(ctx, "hooks", fileId)
if err != nil {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "message": "Can't delete"}`))
return
}
image := "webhook"
// This is here to force stop and remove the old webhook
err = stopWebhook(image, fileId)
if err != nil {
log.Printf("Container stop issue for %s-%s: %s", image, fileId, err)
resp.Write([]byte(`{"success": false, "message": "Couldn't stop webhook"}`))
return
}
resp.WriteHeader(200)
resp.Write([]byte(`{"success": true, "message": "Deleted webhook"}`))
}
// Starts a new webhook
func handleStartHookDocker(resp http.ResponseWriter, request *http.Request) {
cors := handleCors(resp, request)
if cors {
return
}
location := strings.Split(request.URL.String(), "/")
var fileId string
if location[1] == "api" {
if len(location) <= 4 {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
fileId = location[4]
}
if len(fileId) != 32 {
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false, "message": "ID not valid"}`))
return
}
ctx := context.Background()
hook, err := getHook(ctx, fileId)
if err != nil {
log.Printf("Failed getting hook: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
if len(hook.Info.Url) == 0 {
log.Printf("Hook url can't be empty.")
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
log.Printf("Status: %s", hook.Status)
log.Printf("Running: %t", hook.Running)
if hook.Running || hook.Status == "Running" {
message := fmt.Sprintf("Error: %s is already running", hook.Id)
log.Println(message)
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "%s"}`, message)))
return
}
// FIXME - verify?
// FIXME - static port? Generate from available range.
image := "webhook"
filepath := "/webhook"
baseUrl := "http://localhost"
callbackUrl := "http://localhost:8001"
// This is here to force stop and remove the old webhook
err = stopWebhook(image, fileId)
if err != nil {
log.Printf("Container stop issue for %s-%s: %s", image, fileId, err)
}
// Dynamic ish ports
var startPort int64 = 5001
var endPort int64 = 5010
port := findAvailablePorts(startPort, endPort)
if len(port) == 0 {
message := fmt.Sprintf("Not ports available in the range %d-%d", startPort, endPort)
log.Println(message)
resp.WriteHeader(401)
resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "%s"}`, message)))
return
}
hook.Status = "running"
hook.Running = true
// Set this for more than just hooks?
if hook.Type == "webhook" {
hook.Info.Url = fmt.Sprintf("%s:%s%s", baseUrl, port, filepath)
}
err = setHook(ctx, *hook)
if err != nil {
log.Printf("Failed setting hook: %s", err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
// Cloud run? Let's make a generic webhook that can be deployed easily
log.Printf("Should run a webhook with the following: \nUrl: %s\nId: %s\n", hook.Info.Url, hook.Id)
// FIXME - set port based on what the user specified / what was generated
// FIXME - add nonstatic APIKEY
apiKey := "ASD"
err = deployWebhook(image, fileId, filepath, port, callbackUrl, apiKey)
if err != nil {
log.Printf("Failed starting container %s-%s: %s", image, fileId, err)
resp.WriteHeader(401)
resp.Write([]byte(`{"success": false}`))
return
}
// FIXME - get some real data?
log.Printf("Successfully started %s-%s on port %s with filepath %s", image, fileId, port, filepath)
resp.WriteHeader(200)
resp.Write([]byte(`{"success": true, "message": "Started webhook"}`))
return
}
func hookTest() {
var hook Hook
err := json.Unmarshal([]byte(webhook), &hook)
log.Println(webhook)
if err != nil {
log.Printf("Failed hook unmarshaling: %s", err)
return
}
ctx := context.Background()
err = setHook(ctx, hook)
if err != nil {
log.Printf("Failed setting hook: %s", err)
}
returnHook, err := getHook(ctx, hook.Id)
if err != nil {
log.Printf("Failed getting hook: %s", err)
}
if len(returnHook.Id) > 0 {
log.Printf("Success! - %s", returnHook.Id)
}
}
@@ -0,0 +1,26 @@
# Base our app image off of the WALKOFF App SDK image
FROM frikky/shuffle:app_sdk as base
# We're going to stage away all of the bloat from the build tools so lets create a builder stage
FROM base as builder
# Install all alpine build tools needed for our pip installs
RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev
# Install all of our pip packages in a single directory that we can copy to our base image later
RUN mkdir /install
WORKDIR /install
COPY requirements.txt /requirements.txt
RUN pip install --prefix="/install" -r /requirements.txt
# Switch back to our base image and copy in all of our built packages and source code
FROM base
COPY --from=builder /install /usr/local
COPY src /app
# Install any binary dependencies needed in our final image - this can be a lot of different stuff
RUN apk --no-cache add --update libmagic
# Finally, lets run our app!
WORKDIR /app
CMD python app.py --log-level DEBUG
@@ -0,0 +1,3 @@
# No extra requirements needed
requests
urllib3
@@ -0,0 +1,26 @@
# Base our app image off of the WALKOFF App SDK image
FROM frikky/shuffle:app_sdk as base
# We're going to stage away all of the bloat from the build tools so lets create a builder stage
FROM base as builder
# Install all alpine build tools needed for our pip installs
RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev
# Install all of our pip packages in a single directory that we can copy to our base image later
RUN mkdir /install
WORKDIR /install
COPY requirements.txt /requirements.txt
RUN pip install --prefix="/install" -r /requirements.txt
# Switch back to our base image and copy in all of our built packages and source code
FROM base
COPY --from=builder /install /usr/local
COPY src /app
# Install any binary dependencies needed in our final image - this can be a lot of different stuff
RUN apk --no-cache add --update libmagic
# Finally, lets run our app!
WORKDIR /app
CMD python app.py --log-level DEBUG
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
# No extra requirements needed
requests
urllib3
@@ -0,0 +1,29 @@
import requests
import asyncio
import json
from walkoff_app_sdk.app_base import AppBase
class Asd37fff3ea5fa10cdde521f21134320c26(AppBase):
"""
Autogenerated class by Shuffler
"""
__version__ = "1.0"
app_name = "Asd37fff3ea5fa10cdde521f21134320c26"
def __init__(self, redis, logger, console_logger=None):
self.verify = False
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
super().__init__(redis, logger, console_logger)
async def get_hello(self, apikey):
headers={}
url=f"https://google.com/lol"
headers["what"] = apikey
return requests.get(url, headers=headers).text
if __name__ == "__main__":
asyncio.run(Asd37fff3ea5fa10cdde521f21134320c26.run(), debug=True)
@@ -0,0 +1,26 @@
# Base our app image off of the WALKOFF App SDK image
FROM frikky/shuffle:app_sdk as base
# We're going to stage away all of the bloat from the build tools so lets create a builder stage
FROM base as builder
# Install all alpine build tools needed for our pip installs
RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev
# Install all of our pip packages in a single directory that we can copy to our base image later
RUN mkdir /install
WORKDIR /install
COPY requirements.txt /requirements.txt
RUN pip install --prefix="/install" -r /requirements.txt
# Switch back to our base image and copy in all of our built packages and source code
FROM base
COPY --from=builder /install /usr/local
COPY src /app
# Install any binary dependencies needed in our final image - this can be a lot of different stuff
RUN apk --no-cache add --update libmagic
# Finally, lets run our app!
WORKDIR /app
CMD python app.py --log-level DEBUG
@@ -0,0 +1,193 @@
name: Uber API
is_valid: true
id: 6a707aa0-892b-42a0-b0a4-b10b6b56e6e5
link: https://api.uber.com
app_version: 1.0.0
generated: true
sharing: false
verified: false
tested: false
owner: 2501f368-edf2-4fee-bb7e-457d65d7410c
private_id: 547f1803-edf7-433f-8016-d508ed978f65
description: Move your app forward with the Uber API
environment: cloud
smallimage: ""
large_image: ""
contact_info:
name: ""
url: ""
actions:
- description: The Price Estimates endpoint returns an estimated price range for each
product offered at a given location. The price estimate is provided as a formatted
string with the full price range and the localized currency symbol.<br><br>The
response also includes low and high estimates, and the [ISO 4217](http://en.wikipedia.org/wiki/ISO_4217)
currency code for situations requiring currency conversion. When surge is active
for a particular product, its surge_multiplier will be greater than 1, but the
price estimate already factors in this multiplier.
name: get_price_estimates
nodetype: action
environment: cloud
sharing: false
privateid: ""
appid: ""
tested: false
parameters:
- description: Latitude component of start location.
name: start_latitude
example: ""
multiline: false
required: true
schema:
type: string
- description: Longitude component of start location.
name: start_longitude
example: ""
multiline: false
required: true
schema:
type: string
- description: Latitude component of end location.
name: end_latitude
example: ""
multiline: false
required: true
schema:
type: string
- description: Longitude component of end location.
name: end_longitude
example: ""
multiline: false
required: true
schema:
type: string
returns:
schema:
type: string
- description: The Time Estimates endpoint returns ETAs for all products offered at
a given location, with the responses expressed as integers in seconds. We recommend
that this endpoint be called every minute to provide the most accurate, up-to-date
ETAs.
name: get_time_estimates
nodetype: action
environment: cloud
sharing: false
privateid: ""
appid: ""
tested: false
parameters:
- description: Latitude component of start location.
name: start_latitude
example: ""
multiline: false
required: true
schema:
type: string
- description: Longitude component of start location.
name: start_longitude
example: ""
multiline: false
required: true
schema:
type: string
- description: Unique customer identifier to be used for experience customization.
name: customer_uuid
example: ""
multiline: false
required: false
schema:
type: string
- description: Unique identifier representing a specific product for a given latitude
& longitude.
name: product_id
example: ""
multiline: false
required: false
schema:
type: string
returns:
schema:
type: string
- description: The User Activity endpoint returns data about a user's lifetime activity
with Uber. The response will include pickup locations and times, dropoff locations
and times, the distance of past requests, and information about which products
were requested.<br><br>The history array in the response will have a maximum length
based on the limit parameter. The response value count may exceed limit, therefore
subsequent API requests may be necessary.
name: get_user_activity
nodetype: action
environment: cloud
sharing: false
privateid: ""
appid: ""
tested: false
parameters:
- description: Offset the list of returned results by this amount. Default is zero.
name: offset
example: ""
multiline: false
required: false
schema:
type: string
- description: Number of items to retrieve. Default is 5, maximum is 100.
name: limit
example: ""
multiline: false
required: false
schema:
type: string
returns:
schema:
type: string
- description: The User Profile endpoint returns information about the Uber user that
has authorized with the application.
name: get_user_profile
nodetype: action
environment: cloud
sharing: false
privateid: ""
appid: ""
tested: false
parameters: []
returns:
schema:
type: string
- description: The Products endpoint returns information about the Uber products offered
at a given location. The response includes the display name and other details
about each product, and lists the products in the proper display order.
name: get_product_types
nodetype: action
environment: cloud
sharing: false
privateid: ""
appid: ""
tested: false
parameters:
- description: Latitude component of location.
name: latitude
example: ""
multiline: false
required: true
schema:
type: string
- description: Longitude component of location.
name: longitude
example: ""
multiline: false
required: true
schema:
type: string
returns:
schema:
type: string
authentication:
required: true
parameters:
- description: ""
id: ""
name: ""
example: ""
value: ""
multiline: false
required: true
in: ""
scheme: ""
@@ -0,0 +1,3 @@
# No extra requirements needed
requests
urllib3
@@ -0,0 +1,66 @@
import requests
import asyncio
import json
import urllib3
from walkoff_app_sdk.app_base import AppBase
class UberAPI547f1803edf7433f8016d508ed978f65(AppBase):
"""
Autogenerated class by Shuffler
"""
__version__ = "1.0"
app_name = "UberAPI547f1803edf7433f8016d508ed978f65"
def __init__(self, redis, logger, console_logger=None):
self.verify = False
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
super().__init__(redis, logger, console_logger)
async def get_price_estimates(self, start_latitude, start_longitude, end_latitude, end_longitude):
headers={}
url=f"https://api.uber.com/estimates/price?start_latitude={start_latitude}&start_longitude={start_longitude}&end_latitude={end_latitude}&end_longitude={end_longitude}"
return requests.get(url, headers=headers).text
async def get_time_estimates(self, start_latitude, start_longitude, customer_uuid="", product_id="", ):
headers={}
url=f"https://api.uber.com/estimates/time?start_latitude={start_latitude}&start_longitude={start_longitude}"
if customer_uuid:
url += f"&customer_uuid={customer_uuid}"
if product_id:
url += f"&product_id={product_id}"
return requests.get(url, headers=headers).text
async def get_user_activity(self, offset="", limit="", ):
headers={}
url=f"https://api.uber.com/history"
if offset:
url += f"&offset={offset}"
if limit:
url += f"&limit={limit}"
return requests.get(url, headers=headers).text
async def get_user_profile(self):
headers={}
url=f"https://api.uber.com/me"
return requests.get(url, headers=headers).text
async def get_product_types(self, latitude, longitude):
headers={}
url=f"https://api.uber.com/products?latitude={latitude}&longitude={longitude}"
return requests.get(url, headers=headers).text
if __name__ == "__main__":
asyncio.run(UberAPI547f1803edf7433f8016d508ed978f65.run(), debug=True)
@@ -0,0 +1,26 @@
# Base our app image off of the WALKOFF App SDK image
FROM frikky/shuffle:app_sdk as base
# We're going to stage away all of the bloat from the build tools so lets create a builder stage
FROM base as builder
# Install all alpine build tools needed for our pip installs
RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev
# Install all of our pip packages in a single directory that we can copy to our base image later
RUN mkdir /install
WORKDIR /install
COPY requirements.txt /requirements.txt
RUN pip install --prefix="/install" -r /requirements.txt
# Switch back to our base image and copy in all of our built packages and source code
FROM base
COPY --from=builder /install /usr/local
COPY src /app
# Install any binary dependencies needed in our final image - this can be a lot of different stuff
RUN apk --no-cache add --update libmagic
# Finally, lets run our app!
WORKDIR /app
CMD python app.py --log-level DEBUG
@@ -0,0 +1,193 @@
name: Uber API
is_valid: true
id: 547f1803-edf7-433f-8016-d508ed978f65
link: https://api.uber.com
app_version: 1.0.0
generated: true
sharing: false
verified: false
tested: false
owner: 2501f368-edf2-4fee-bb7e-457d65d7410c
private_id: 795ed46f-2dbd-43d6-9d0c-1f6b9cce14ea
description: Move your app forward with the Uber API
environment: cloud
smallimage: ""
large_image: ""
contact_info:
name: ""
url: ""
actions:
- description: The Price Estimates endpoint returns an estimated price range for each
product offered at a given location. The price estimate is provided as a formatted
string with the full price range and the localized currency symbol.<br><br>The
response also includes low and high estimates, and the [ISO 4217](http://en.wikipedia.org/wiki/ISO_4217)
currency code for situations requiring currency conversion. When surge is active
for a particular product, its surge_multiplier will be greater than 1, but the
price estimate already factors in this multiplier.
name: get_price_estimates
nodetype: action
environment: cloud
sharing: false
privateid: ""
appid: ""
tested: false
parameters:
- description: Latitude component of start location.
name: start_latitude
example: ""
multiline: false
required: true
schema:
type: string
- description: Longitude component of start location.
name: start_longitude
example: ""
multiline: false
required: true
schema:
type: string
- description: Latitude component of end location.
name: end_latitude
example: ""
multiline: false
required: true
schema:
type: string
- description: Longitude component of end location.
name: end_longitude
example: ""
multiline: false
required: true
schema:
type: string
returns:
schema:
type: string
- description: The Time Estimates endpoint returns ETAs for all products offered at
a given location, with the responses expressed as integers in seconds. We recommend
that this endpoint be called every minute to provide the most accurate, up-to-date
ETAs.
name: get_time_estimates
nodetype: action
environment: cloud
sharing: false
privateid: ""
appid: ""
tested: false
parameters:
- description: Latitude component of start location.
name: start_latitude
example: ""
multiline: false
required: true
schema:
type: string
- description: Longitude component of start location.
name: start_longitude
example: ""
multiline: false
required: true
schema:
type: string
- description: Unique customer identifier to be used for experience customization.
name: customer_uuid
example: ""
multiline: false
required: false
schema:
type: string
- description: Unique identifier representing a specific product for a given latitude
& longitude.
name: product_id
example: ""
multiline: false
required: false
schema:
type: string
returns:
schema:
type: string
- description: The User Activity endpoint returns data about a user's lifetime activity
with Uber. The response will include pickup locations and times, dropoff locations
and times, the distance of past requests, and information about which products
were requested.<br><br>The history array in the response will have a maximum length
based on the limit parameter. The response value count may exceed limit, therefore
subsequent API requests may be necessary.
name: get_user_activity
nodetype: action
environment: cloud
sharing: false
privateid: ""
appid: ""
tested: false
parameters:
- description: Offset the list of returned results by this amount. Default is zero.
name: offset
example: ""
multiline: false
required: false
schema:
type: string
- description: Number of items to retrieve. Default is 5, maximum is 100.
name: limit
example: ""
multiline: false
required: false
schema:
type: string
returns:
schema:
type: string
- description: The User Profile endpoint returns information about the Uber user that
has authorized with the application.
name: get_user_profile
nodetype: action
environment: cloud
sharing: false
privateid: ""
appid: ""
tested: false
parameters: []
returns:
schema:
type: string
- description: The Products endpoint returns information about the Uber products offered
at a given location. The response includes the display name and other details
about each product, and lists the products in the proper display order.
name: get_product_types
nodetype: action
environment: cloud
sharing: false
privateid: ""
appid: ""
tested: false
parameters:
- description: Latitude component of location.
name: latitude
example: ""
multiline: false
required: true
schema:
type: string
- description: Longitude component of location.
name: longitude
example: ""
multiline: false
required: true
schema:
type: string
returns:
schema:
type: string
authentication:
required: true
parameters:
- description: ""
id: ""
name: ""
example: ""
value: ""
multiline: false
required: true
in: ""
scheme: ""
@@ -0,0 +1,3 @@
# No extra requirements needed
requests
urllib3
@@ -0,0 +1,66 @@
import requests
import asyncio
import json
import urllib3
from walkoff_app_sdk.app_base import AppBase
class UberAPI795ed46f2dbd43d69d0c1f6b9cce14ea(AppBase):
"""
Autogenerated class by Shuffler
"""
__version__ = "1.0"
app_name = "UberAPI795ed46f2dbd43d69d0c1f6b9cce14ea"
def __init__(self, redis, logger, console_logger=None):
self.verify = False
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
super().__init__(redis, logger, console_logger)
async def get_price_estimates(self, start_latitude, start_longitude, end_latitude, end_longitude):
headers={}
url=f"https://api.uber.com/estimates/price?start_latitude={start_latitude}&start_longitude={start_longitude}&end_latitude={end_latitude}&end_longitude={end_longitude}"
return requests.get(url, headers=headers).text
async def get_time_estimates(self, start_latitude, start_longitude, customer_uuid="", product_id="", ):
headers={}
url=f"https://api.uber.com/estimates/time?start_latitude={start_latitude}&start_longitude={start_longitude}"
if customer_uuid:
url += f"&customer_uuid={customer_uuid}"
if product_id:
url += f"&product_id={product_id}"
return requests.get(url, headers=headers).text
async def get_user_activity(self, offset="", limit="", ):
headers={}
url=f"https://api.uber.com/history"
if offset:
url += f"&offset={offset}"
if limit:
url += f"&limit={limit}"
return requests.get(url, headers=headers).text
async def get_user_profile(self):
headers={}
url=f"https://api.uber.com/me"
return requests.get(url, headers=headers).text
async def get_product_types(self, latitude, longitude):
headers={}
url=f"https://api.uber.com/products?latitude={latitude}&longitude={longitude}"
return requests.get(url, headers=headers).text
if __name__ == "__main__":
asyncio.run(UberAPI795ed46f2dbd43d69d0c1f6b9cce14ea.run(), debug=True)
@@ -0,0 +1,26 @@
# Base our app image off of the WALKOFF App SDK image
FROM frikky/shuffle:app_sdk as base
# We're going to stage away all of the bloat from the build tools so lets create a builder stage
FROM base as builder
# Install all alpine build tools needed for our pip installs
RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev
# Install all of our pip packages in a single directory that we can copy to our base image later
RUN mkdir /install
WORKDIR /install
COPY requirements.txt /requirements.txt
RUN pip install --prefix="/install" -r /requirements.txt
# Switch back to our base image and copy in all of our built packages and source code
FROM base
COPY --from=builder /install /usr/local
COPY src /app
# Install any binary dependencies needed in our final image - this can be a lot of different stuff
RUN apk --no-cache add --update libmagic
# Finally, lets run our app!
WORKDIR /app
CMD python app.py --log-level DEBUG
@@ -0,0 +1,228 @@
name: Uber API
is_valid: true
id: 795ed46f-2dbd-43d6-9d0c-1f6b9cce14ea
link: ""
app_version: 1.0.0
generated: true
sharing: false
verified: false
tested: false
owner: 2501f368-edf2-4fee-bb7e-457d65d7410c
private_id: f97a44dd-f1c3-48ae-b46f-ceb19ce3039c
description: Move your app forward with the Uber API
environment: cloud
smallimage: ""
large_image: ""
contact_info:
name: ""
url: ""
actions:
- description: The Time Estimates endpoint returns ETAs for all products offered at
a given location, with the responses expressed as integers in seconds. We recommend
that this endpoint be called every minute to provide the most accurate, up-to-date
ETAs.
name: get_time_estimates
nodetype: action
environment: cloud
sharing: false
privateid: ""
appid: ""
tested: false
parameters:
- description: The URL of the app
name: url
example: ""
multiline: false
required: true
schema:
type: string
- description: Latitude component of start location.
name: start_latitude
example: ""
multiline: false
required: true
schema:
type: string
- description: Longitude component of start location.
name: start_longitude
example: ""
multiline: false
required: true
schema:
type: string
- description: Unique customer identifier to be used for experience customization.
name: customer_uuid
example: ""
multiline: false
required: false
schema:
type: string
- description: Unique identifier representing a specific product for a given latitude
& longitude.
name: product_id
example: ""
multiline: false
required: false
schema:
type: string
returns:
schema:
type: string
- description: The User Activity endpoint returns data about a user's lifetime activity
with Uber. The response will include pickup locations and times, dropoff locations
and times, the distance of past requests, and information about which products
were requested.<br><br>The history array in the response will have a maximum length
based on the limit parameter. The response value count may exceed limit, therefore
subsequent API requests may be necessary.
name: get_user_activity
nodetype: action
environment: cloud
sharing: false
privateid: ""
appid: ""
tested: false
parameters:
- description: The URL of the app
name: url
example: ""
multiline: false
required: true
schema:
type: string
- description: Offset the list of returned results by this amount. Default is zero.
name: offset
example: ""
multiline: false
required: false
schema:
type: string
- description: Number of items to retrieve. Default is 5, maximum is 100.
name: limit
example: ""
multiline: false
required: false
schema:
type: string
returns:
schema:
type: string
- description: The User Profile endpoint returns information about the Uber user that
has authorized with the application.
name: get_user_profile
nodetype: action
environment: cloud
sharing: false
privateid: ""
appid: ""
tested: false
parameters:
- description: The URL of the app
name: url
example: ""
multiline: false
required: true
schema:
type: string
returns:
schema:
type: string
- description: The Products endpoint returns information about the Uber products offered
at a given location. The response includes the display name and other details
about each product, and lists the products in the proper display order.
name: get_product_types
nodetype: action
environment: cloud
sharing: false
privateid: ""
appid: ""
tested: false
parameters:
- description: The URL of the app
name: url
example: ""
multiline: false
required: true
schema:
type: string
- description: Latitude component of location.
name: latitude
example: ""
multiline: false
required: true
schema:
type: string
- description: Longitude component of location.
name: longitude
example: ""
multiline: false
required: true
schema:
type: string
returns:
schema:
type: string
- description: The Price Estimates endpoint returns an estimated price range for each
product offered at a given location. The price estimate is provided as a formatted
string with the full price range and the localized currency symbol.<br><br>The
response also includes low and high estimates, and the [ISO 4217](http://en.wikipedia.org/wiki/ISO_4217)
currency code for situations requiring currency conversion. When surge is active
for a particular product, its surge_multiplier will be greater than 1, but the
price estimate already factors in this multiplier.
name: get_price_estimates
nodetype: action
environment: cloud
sharing: false
privateid: ""
appid: ""
tested: false
parameters:
- description: The URL of the app
name: url
example: ""
multiline: false
required: true
schema:
type: string
- description: Latitude component of start location.
name: start_latitude
example: ""
multiline: false
required: true
schema:
type: string
- description: Longitude component of start location.
name: start_longitude
example: ""
multiline: false
required: true
schema:
type: string
- description: Latitude component of end location.
name: end_latitude
example: ""
multiline: false
required: true
schema:
type: string
- description: Longitude component of end location.
name: end_longitude
example: ""
multiline: false
required: true
schema:
type: string
returns:
schema:
type: string
authentication:
required: true
parameters:
- description: ""
id: ""
name: ""
example: ""
value: ""
multiline: false
required: true
in: ""
scheme: ""
@@ -0,0 +1,3 @@
# No extra requirements needed
requests
urllib3
@@ -0,0 +1,66 @@
import requests
import asyncio
import json
import urllib3
from walkoff_app_sdk.app_base import AppBase
class UberAPIf97a44ddf1c348aeb46fceb19ce3039c(AppBase):
"""
Autogenerated class by Shuffler
"""
__version__ = "1.0"
app_name = "UberAPIf97a44ddf1c348aeb46fceb19ce3039c"
def __init__(self, redis, logger, console_logger=None):
self.verify = False
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
super().__init__(redis, logger, console_logger)
async def get_time_estimates(self, baseurl, start_latitude, start_longitude, customer_uuid="", product_id="", ):
headers={}
url=f"{baseurl}/estimates/time?start_latitude={start_latitude}&start_longitude={start_longitude}"
if customer_uuid:
url += f"&customer_uuid={customer_uuid}"
if product_id:
url += f"&product_id={product_id}"
return requests.get(url, headers=headers).text
async def get_user_activity(self, baseurl, offset="", limit="", ):
headers={}
url=f"{baseurl}/history"
if offset:
url += f"&offset={offset}"
if limit:
url += f"&limit={limit}"
return requests.get(url, headers=headers).text
async def get_user_profile(self, baseurl):
headers={}
url=f"{baseurl}/me"
return requests.get(url, headers=headers).text
async def get_product_types(self, baseurl, latitude, longitude):
headers={}
url=f"{baseurl}/products?latitude={latitude}&longitude={longitude}"
return requests.get(url, headers=headers).text
async def get_price_estimates(self, baseurl, start_latitude, start_longitude, end_latitude, end_longitude):
headers={}
url=f"{baseurl}/estimates/price?start_latitude={start_latitude}&start_longitude={start_longitude}&end_latitude={end_latitude}&end_longitude={end_longitude}"
return requests.get(url, headers=headers).text
if __name__ == "__main__":
asyncio.run(UberAPIf97a44ddf1c348aeb46fceb19ce3039c.run(), debug=True)
@@ -0,0 +1,26 @@
# Base our app image off of the WALKOFF App SDK image
FROM frikky/shuffle:app_sdk as base
# We're going to stage away all of the bloat from the build tools so lets create a builder stage
FROM base as builder
# Install all alpine build tools needed for our pip installs
RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev
# Install all of our pip packages in a single directory that we can copy to our base image later
RUN mkdir /install
WORKDIR /install
COPY requirements.txt /requirements.txt
RUN pip install --prefix="/install" -r /requirements.txt
# Switch back to our base image and copy in all of our built packages and source code
FROM base
COPY --from=builder /install /usr/local
COPY src /app
# Install any binary dependencies needed in our final image - this can be a lot of different stuff
RUN apk --no-cache add --update libmagic
# Finally, lets run our app!
WORKDIR /app
CMD python app.py --log-level DEBUG
@@ -0,0 +1,31 @@
name: a
is_valid: true
id: 31e2332c-8baa-41e9-98af-c2457d64d946
link: ""
app_version: 1.0.0
generated: true
sharing: false
verified: false
tested: false
owner: c2dbe917-f982-40ed-9045-462872c4ca1e
private_id: 757f74fead843a8d9b5a954d2fadfa69
description: ""
environment: cloud
smallimage: ""
large_image: ""
contact_info:
name: ""
url: ""
actions: []
authentication:
required: true
parameters:
- description: ""
id: ""
name: ""
example: ""
value: ""
multiline: false
required: true
in: ""
scheme: ""
@@ -0,0 +1,3 @@
# No extra requirements needed
requests
urllib3
@@ -0,0 +1,23 @@
import requests
import asyncio
import json
from walkoff_app_sdk.app_base import AppBase
class a757f74fead843a8d9b5a954d2fadfa69(AppBase):
"""
Autogenerated class by Shuffler
"""
__version__ = "1.0"
app_name = "a757f74fead843a8d9b5a954d2fadfa69"
def __init__(self, redis, logger, console_logger=None):
self.verify = False
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
super().__init__(redis, logger, console_logger)
if __name__ == "__main__":
asyncio.run(a757f74fead843a8d9b5a954d2fadfa69.run(), debug=True)
@@ -0,0 +1,26 @@
# Base our app image off of the WALKOFF App SDK image
FROM frikky/shuffle:app_sdk as base
# We're going to stage away all of the bloat from the build tools so lets create a builder stage
FROM base as builder
# Install all alpine build tools needed for our pip installs
RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev
# Install all of our pip packages in a single directory that we can copy to our base image later
RUN mkdir /install
WORKDIR /install
COPY requirements.txt /requirements.txt
RUN pip install --prefix="/install" -r /requirements.txt
# Switch back to our base image and copy in all of our built packages and source code
FROM base
COPY --from=builder /install /usr/local
COPY src /app
# Install any binary dependencies needed in our final image - this can be a lot of different stuff
RUN apk --no-cache add --update libmagic
# Finally, lets run our app!
WORKDIR /app
CMD python app.py --log-level DEBUG
@@ -0,0 +1,31 @@
name: test
is_valid: true
id: a29d4143-fa3b-4456-86c4-fee2d402ddf6
link: https://test.com
app_version: 1.0.0
generated: true
sharing: false
verified: false
tested: false
owner: c2dbe917-f982-40ed-9045-462872c4ca1e
private_id: f383aa4f34d36802daf465e2af5f7aa1
description: test
environment: cloud
smallimage: ""
large_image: ""
contact_info:
name: ""
url: ""
actions: []
authentication:
required: true
parameters:
- description: ""
id: ""
name: ""
example: ""
value: ""
multiline: false
required: true
in: ""
scheme: ""
@@ -0,0 +1,3 @@
# No extra requirements needed
requests
urllib3
@@ -0,0 +1,23 @@
import requests
import asyncio
import json
from walkoff_app_sdk.app_base import AppBase
class testf383aa4f34d36802daf465e2af5f7aa1(AppBase):
"""
Autogenerated class by Shuffler
"""
__version__ = "1.0"
app_name = "testf383aa4f34d36802daf465e2af5f7aa1"
def __init__(self, redis, logger, console_logger=None):
self.verify = False
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
super().__init__(redis, logger, console_logger)
if __name__ == "__main__":
asyncio.run(testf383aa4f34d36802daf465e2af5f7aa1.run(), debug=True)
+31
View File
@@ -0,0 +1,31 @@
module shuffle
go 1.13
require (
cloud.google.com/go v0.57.0
cloud.google.com/go/datastore v1.1.0
cloud.google.com/go/pubsub v1.3.1
cloud.google.com/go/storage v1.7.0
github.com/basgys/goxml2json v1.1.0
github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/docker/docker v1.13.1
github.com/docker/go-connections v0.4.0
github.com/docker/go-units v0.4.0 // indirect
github.com/getkin/kin-openapi v0.8.0
github.com/ghodss/yaml v1.0.0
github.com/go-git/go-billy/v5 v5.0.0
github.com/go-git/go-git/v5 v5.0.0
github.com/google/go-github/v28 v28.1.1
github.com/gorilla/mux v1.7.4
github.com/h2non/filetype v1.0.12
github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/satori/go.uuid v1.2.0
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
google.golang.org/api v0.23.0
google.golang.org/appengine v1.6.6
google.golang.org/genproto v0.0.0-20200507105951-43844f6eee31
gopkg.in/yaml.v2 v2.2.8
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86
)
+400
View File
@@ -0,0 +1,400 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0 h1:EpMNVUorLiZIELdMZbCYX/ByTFCdoYopYAGxaGVz9ms=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.6.0/go.mod h1:hyFDG0qSGdHNz8Q6nDN8rYIkld0q/+5uBZaelxiDLfE=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.7.0 h1:DzdLPI8Em+DEk7IzA2a10ivq3mxIEASC9GeNJ6FFt5Q=
cloud.google.com/go/storage v1.7.0/go.mod h1:jGMIBwF+L/tL6WN/W5InNgYYu4HP0DvGB6rQ1mufWfs=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/basgys/goxml2json v1.1.0 h1:4ln5i4rseYfXNd86lGEB+Vi652IsIXIvggKM/BhUKVw=
github.com/basgys/goxml2json v1.1.0/go.mod h1:wH7a5Np/Q4QoECFIU8zTQlZwZkrilY0itPfecMw41Dw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo=
github.com/docker/docker v1.13.1/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/getkin/kin-openapi v0.8.0 h1:a6TQjTqwkyscC4/hShJX7WhCVE+4bi9lzw61XHQW5hE=
github.com/getkin/kin-openapi v0.8.0/go.mod h1:zZQMFkVgRHCdhgb6ihCTIo9dyDZFvX0k/xAKqw1FhPw=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4=
github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E=
github.com/go-git/go-billy v4.2.0+incompatible h1:Z6QtVXd5tjxUtcODLugkJg4WaZnGg13CD8qB9pr+7q0=
github.com/go-git/go-billy/v5 v5.0.0 h1:7NQHvd9FVid8VL4qVUMm8XifBK+2xCoZ2lSk0agRrHM=
github.com/go-git/go-billy/v5 v5.0.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0=
github.com/go-git/go-git v4.7.0+incompatible h1:+W9rgGY4DOKKdX2x6HxSR7HNeTxqiKrOvKnuittYVdA=
github.com/go-git/go-git-fixtures/v4 v4.0.1/go.mod h1:m+ICp2rF3jDhFgEZ/8yziagdT1C+ZpZcrJjappBCDSw=
github.com/go-git/go-git/v5 v5.0.0 h1:k5RWPm4iJwYtfWoxIJy4wJX9ON7ihPeZZYC1fLYDnpg=
github.com/go-git/go-git/v5 v5.0.0/go.mod h1:oYD8y9kWsGINPFJoLdaScGCN6dlKg23blmClfZwtUVA=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY=
github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo=
github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/h2non/filetype v1.0.12 h1:yHCsIe0y2cvbDARtJhGBTD2ecvqMSTvlIcph9En/Zao=
github.com/h2non/filetype v1.0.12/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY=
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70=
github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88=
golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5 h1:WQ8q63x+f/zpC8Ac1s9wLElVoHhm32p6tudrU72n1QA=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200409092240-59c9f1ba88fa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e h1:hq86ru83GdWTlfQFZGO4nZJTU4Bs2wfHl8oFHRaXsfc=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200409170454-77362c5149f0/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.21.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.23.0 h1:YlvGEOq2NA2my8cZ/9V8BcEO9okD48FlJcdqN0xJL3s=
google.golang.org/api v0.23.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200409111301-baae70f3302d/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200507105951-43844f6eee31 h1:Bz1qTn2YRWV+9OKJtxHJiQKCiXIdf+kwuKXdt9cBxyU=
google.golang.org/genproto v0.0.0-20200507105951-43844f6eee31/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.28.1/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86 h1:OfFoIUYv/me30yv7XlMy4F9RJw8DEm8WQ6QG1Ph4bH0=
gopkg.in/yaml.v3 v3.0.0-20200506231410-2ff61e1afc86/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.
+15
View File
@@ -0,0 +1,15 @@
#!/bin/sh
# docker stop nginx
# docker rm nginx
# docker rmi nginx
#
# echo "Running build for website"
# sudo npm run build
# docker build . -t nginx
echo "Starting server"
docker run -it \
-p 5001:5001 \
-v /var/run/docker.sock:/var/run/docker.sock \
--env DATASTORE_EMULATOR_HOST=192.168.3.6:8000 \
frikky/shuffle:backend
+2
View File
@@ -0,0 +1,2 @@
curl http://localhost:5001/api/v1/execution_cleanup -H "Authorization: Bearer e08c6f22-9a55-4557-b008-04388cc51fb0"
+5
View File
@@ -0,0 +1,5 @@
#!/bin/sh
curl -XPOST http://localhost:5001/api/v1/workflows/1d9d8ce2-566e-4c3f-8a37-5d6c7d2000b5/execute -d '{"execution_argument":""}' -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26"
curl -XPOST http://localhost:5001/api/v1/workflows/1d9d8ce2-566e-4c3f-8a37-5d6c7d2000b5/execute -d '{"execution_argument":""}' -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6IjYwZjQwNjBlNThkNzVmZDNmNzBiZWZmODhjNzk0YTc3NTMyN2FhMzEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL3NodWZmbGVyLmlvL2FwaS92MS93b3JrZmxvd3MvMWQ5ZDhjZTItNTY2ZS00YzNmLThhMzctNWQ2YzdkMjAwMGI1L2V4ZWN1dGUiLCJhenAiOiIxMDMwNzY3ODIwNjE0MjQ2MTg0MjIiLCJlbWFpbCI6InNjaGVkdWxlckBzaHVmZmxlLTI0MTUxNy5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE1NjU1Mjc1NTEsImlhdCI6MTU2NTUyMzk1MSwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAzMDc2NzgyMDYxNDI0NjE4NDIyIn0.r0EDq9fjhf_5CPTiltyfk_L3uYJp577Uy0yYPcCAl2nv50_z_oUtbWGBpQLL8gcj-NGd3g4E52Qur8k6hCMIQweLS6WAb1279vGffEoCNDfkWb3Oy-yJGP1kzwLvqFJqnHLkSWYXNWvSyWnEimW8Rryx_m1BXS5wcA8l4NIr83kS7fPZrTwjnwFSeGSThwk91DVARzapQb8r0GEgOUyHZ1aBXnV98mikzSUt-5xFKe9eMdD22YJAj0Ru-DxAxs5nOqghX4PMRysWjshjOMrlR1piPWxqAmewp8YKZDCQ5gXskpeAFBDoULT971Wsx_NCohnJsFqx1JfPS9ZYMTW2oQ"
+24
View File
@@ -0,0 +1,24 @@
#curl localhost:5000/api/v1/hooks
# Starts a webhook
# curl localhost:5000/api/v1/hooks/d6ef8912e8bd37776e654cbc14c2629c/start
# Runs a request towards the webhook created
#curl -XPOST localhost:5002/webhook -d '{"helo": "hi"}'
# Gets the ID of a new hook
#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"}}'
#curl -X POST localhost:5000/api/v1/hooks/new -d '{"name": "asd", "description": "hola", "type": "webhook"}'
#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
#jcurl http://localhost:5001/api/v1/hooks/b4ba07c9-45d4-41f2-b260-83c8e99eba0c -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26"
#curl https://shuffler.io/api/v1/hooks/b4ba07c9-45d4-41f2-b260-83c8e99eba0c -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26"
#curl -X POST "http://localhost:8080" -H "Content-Type:application/json" -H "Authorization: Bearer 144308d0-6aab-4d4f-8bb2-75189281ee26" --data '{"test": {"hello": "HEYOOOO"}}' -v
#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'
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
+69
View File
@@ -0,0 +1,69 @@
# This is a script to test a function by itself
import requests
import json
def invoke(url, headers, message):
# Used for testing
try:
ret = requests.post(url, headers=headers, json=message, timeout=5)
print(ret.text)
print(ret.status_code)
except requests.exceptions.ConnectionError as e:
print(f"Requesterror: {e}")
def invoke_multi(url, headers, message):
cnt = 0
maxcnt = 100
print("Running %d requests towards %s." % (maxcnt, url))
while(1):
try:
ret = requests.post(url, headers=headers, json=message, timeout=1)
print(ret.status_code)
except requests.exceptions.ConnectionError as e:
print(f"Connectionerror: {e}")
except requests.exceptions.ReadTimeout as e:
print(f"Readtimeout: {e}")
cnt += 1
if cnt == maxcnt:
break
print("Done :)")
if __name__ == "__main__":
# Specific thingies for hello_world
message = {
"parameters": [{
"id_": "asd",
"name": "call",
"value": "REPEAT THIS DATA PLEASE THANKS",
"variant": "STATIC_VALUE",
}],
"name": "repeat_back_to_me",
"execution_id": "asd",
"label": "",
"position": "",
"app_name": "hello_world",
"app_version": "1.0.0",
"label": "lul",
"priority": "1",
"id_": "test",
"id": "test",
"authorization": "hey",
}
apikey = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjYwZjQwNjBlNThkNzVmZDNmNzBiZWZmODhjNzk0YTc3NTMyN2FhMzEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL3NodWZmbGVyLmlvL2FwaS92MS93b3JrZmxvd3MvMWQ5ZDhjZTItNTY2ZS00YzNmLThhMzctNWQ2YzdkMjAwMGI1L2V4ZWN1dGUiLCJhenAiOiIxMDMwNzY3ODIwNjE0MjQ2MTg0MjIiLCJlbWFpbCI6InNjaGVkdWxlckBzaHVmZmxlLTI0MTUxNy5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE1NjU1Mjc1NTEsImlhdCI6MTU2NTUyMzk1MSwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAzMDc2NzgyMDYxNDI0NjE4NDIyIn0.r0EDq9fjhf_5CPTiltyfk_L3uYJp577Uy0yYPcCAl2nv50_z_oUtbWGBpQLL8gcj-NGd3g4E52Qur8k6hCMIQweLS6WAb1279vGffEoCNDfkWb3Oy-yJGP1kzwLvqFJqnHLkSWYXNWvSyWnEimW8Rryx_m1BXS5wcA8l4NIr83kS7fPZrTwjnwFSeGSThwk91DVARzapQb8r0GEgOUyHZ1aBXnV98mikzSUt-5xFKe9eMdD22YJAj0Ru-DxAxs5nOqghX4PMRysWjshjOMrlR1piPWxqAmewp8YKZDCQ5gXskpeAFBDoULT971Wsx_NCohnJsFqx1JfPS9ZYMTW2oQ"
headers = {
"Content-Type": "application/json",
"Authorization": f"Bearer {apikey}"
}
location = "europe-west2"
functionname = "hello-world-1-0-6"
project = "shuffler"
url = f"https://{location}-{project}.cloudfunctions.net/{functionname}"
print(url)
invoke(url, headers, message)
#invoke_multi(url, headers, message)
+2
View File
@@ -0,0 +1,2 @@
curl localhost:5000/api/v1/schedules/apps
+2
View File
@@ -0,0 +1,2 @@
# Fails cus of unmarshal
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 eyJhbGciOiJSUzI1NiIsImtpZCI6IjYwZjQwNjBlNThkNzVmZDNmNzBiZWZmODhjNzk0YTc3NTMyN2FhMzEiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL3NodWZmbGVyLmlvL2FwaS92MS93b3JrZmxvd3MvMWQ5ZDhjZTItNTY2ZS00YzNmLThhMzctNWQ2YzdkMjAwMGI1L2V4ZWN1dGUiLCJhenAiOiIxMDMwNzY3ODIwNjE0MjQ2MTg0MjIiLCJlbWFpbCI6InNjaGVkdWxlckBzaHVmZmxlLTI0MTUxNy5pYW0uZ3NlcnZpY2VhY2NvdW50LmNvbSIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE1NjU1Mjc1NTEsImlhdCI6MTU2NTUyMzk1MSwiaXNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTAzMDc2NzgyMDYxNDI0NjE4NDIyIn0.r0EDq9fjhf_5CPTiltyfk_L3uYJp577Uy0yYPcCAl2nv50_z_oUtbWGBpQLL8gcj-NGd3g4E52Qur8k6hCMIQweLS6WAb1279vGffEoCNDfkWb3Oy-yJGP1kzwLvqFJqnHLkSWYXNWvSyWnEimW8Rryx_m1BXS5wcA8l4NIr83kS7fPZrTwjnwFSeGSThwk91DVARzapQb8r0GEgOUyHZ1aBXnV98mikzSUt-5xFKe9eMdD22YJAj0Ru-DxAxs5nOqghX4PMRysWjshjOMrlR1piPWxqAmewp8YKZDCQ5gXskpeAFBDoULT971Wsx_NCohnJsFqx1JfPS9ZYMTW2oQ"
+3
View File
@@ -0,0 +1,3 @@
#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"}'
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"}'
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
# Should give 401
curl localhost:5000/api/v1/uploadResult/asdasd -d {}
echo
# Should give 200 if it exists
curl localhost:5000/api/v1/uploadResult/e07910a06a086c83ba41827aa00b26ed -d '{"title": "helo","description": "wut", "type": "hi", "source": "wutface", "sourceRef": "halvor hei"}'
+1
View File
@@ -0,0 +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"
+4
View File
@@ -0,0 +1,4 @@
#!/bin/bash
curl http://localhost:5001/ws -H "Connections: Upgrade"
#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
View File
@@ -0,0 +1,202 @@
[
{
"actions": [
{
"app_name": "hello_world",
"app_version": "1.0.0",
"errors": [],
"id_": "2686a5d4-531d-158f-6b1a-0c1d23481304",
"is_valid": true,
"label": "check_bool",
"name": "check_bool",
"environment": "cloud",
"parameters": [],
"position": {
"x": 329.98133726556375,
"y": 160.01013778166904
},
"priority": 3
}
],
"branches": [
{
"destination_id": "6478ecae-b10e-88e9-e34d-9bbe6aff393d",
"id_": "5fd6a357-ae33-b1af-5dc2-0306efa28887",
"source_id": "2686a5d4-531d-158f-6b1a-0c1d23481304"
},
{
"destination_id": "2686a5d4-531d-158f-6b1a-0c1d23481304",
"id_": "d46d0b05-5757-5084-c339-70ac63985781",
"source_id": "6478ecae-b10e-88e9-e34d-9bbe6aff393d"
}
],
"conditions": [
{
"app_name": "Builtin",
"app_version": "1.0.0",
"conditional": "",
"errors": [],
"id_": "6478ecae-b10e-88e9-e34d-9bbe6aff393d",
"is_valid": true,
"label": "Condition",
"name": "Condition",
"position": {
"x": 320.97142988802364,
"y": 394.9753467582139
}
}
],
"description": "",
"errors": [],
"id_": "a5f82cfd-0f38-3474-20e2-f757f3718707",
"is_valid": true,
"name": "asd2",
"start": "2686a5d4-531d-158f-6b1a-0c1d23481304",
"tags": [],
"transforms": [],
"triggers": [],
"workflow_variables": []
},
{
"actions": [
{
"app_name": "hello_world",
"app_version": "1.0.0",
"errors": [],
"id_": "51df7c4f-b856-1aca-402b-9fec660b6505",
"is_valid": true,
"label": "wut",
"name": "hello_world",
"parameters": [],
"position": {
"x": 250,
"y": 150
},
"priority": 3
},
{
"app_name": "hello_world",
"app_version": "1.0.0",
"errors": [],
"id_": "36975212-3b9a-2e4c-4ad7-0ee5a6325842",
"is_valid": true,
"label": "check_bool",
"name": "check_bool",
"parameters": [],
"position": {
"x": 241.00207363715955,
"y": 294.9896318142021
},
"priority": 3
}
],
"branches": [
{
"destination_id": "36975212-3b9a-2e4c-4ad7-0ee5a6325842",
"id_": "44231b8f-4331-764e-0c1d-ccbc901d1309",
"source_id": "51df7c4f-b856-1aca-402b-9fec660b6505"
},
{
"destination_id": "51df7c4f-b856-1aca-402b-9fec660b6505",
"id_": "9b3dc561-e879-4877-c32e-ab7149d83b39",
"source_id": "36975212-3b9a-2e4c-4ad7-0ee5a6325842"
}
],
"conditions": [],
"description": "",
"errors": [],
"id_": "4e437698-fc18-29d3-e875-969b57354685",
"is_valid": true,
"name": "hi",
"start": "51df7c4f-b856-1aca-402b-9fec660b6505",
"tags": [],
"transforms": [],
"triggers": [],
"workflow_variables": []
},
{
"actions": [
{
"app_name": "hello_world",
"app_version": "1.0.0",
"errors": [],
"id_": "d0dbd1c4-dd61-6d4a-70e1-ac05dad0fe1f",
"is_valid": true,
"label": "i am bool",
"name": "check_bool",
"parameters": [],
"position": {
"x": 377.9998262128892,
"y": 330.01190441708906
},
"priority": 3
},
{
"app_name": "hello_world",
"app_version": "1.0.0",
"errors": [],
"id_": "39353c1c-b179-152c-f977-615a15a5de37",
"is_valid": true,
"label": "hello_world",
"name": "hello_world",
"parameters": [],
"position": {
"x": 375.2590614780782,
"y": 160.6226941577116
},
"priority": 3
}
],
"branches": [
{
"destination_id": "d0dbd1c4-dd61-6d4a-70e1-ac05dad0fe1f",
"id_": "7c81993f-5fd6-fc83-2f41-8074d6c8dc25",
"source_id": "39353c1c-b179-152c-f977-615a15a5de37"
}
],
"conditions": [],
"description": "",
"errors": [],
"id_": "af8467be-43ca-d38b-3f7e-9aeb922fb21a",
"is_valid": true,
"name": "new!",
"start": "39353c1c-b179-152c-f977-615a15a5de37",
"tags": [],
"transforms": [],
"triggers": [],
"workflow_variables": []
},
{
"actions": [
{
"app_name": "Builtin",
"app_version": "1.0.0",
"errors": [],
"id_": "7dddec9a-b493-8b58-9234-1b74dd9b420a",
"is_valid": true,
"label": "Boolean",
"name": "Boolean",
"parameters": [],
"position": {
"x": 350,
"y": 250
},
"priority": 3
}
],
"branches": [],
"conditions": [],
"description": "",
"errors": [
"Action Builtin.Boolean does not exist"
],
"id_": "07bae551-21e6-c0f5-4db3-81e8a1e8a805",
"is_valid": false,
"name": "wutface",
"start": "7dddec9a-b493-8b58-9234-1b74dd9b420a",
"tags": [],
"transforms": [],
"triggers": [],
"workflow_variables": []
}
]
+23
View File
@@ -0,0 +1,23 @@
#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"}'
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"}'
#curl -X POST http://localhost:5001/api/v1/workflows/streams -H "Content-Type: application/json" \
# -d '{"execution_id": "eaaa8d19-a761-12b8-cac2-f34eb50c3711",
# "result": "hello_result",
# "started_at": 1562309342,
# "authorization": "afcc298d-c6c2-4b0d-8221-1603b44d072d",
# "status": "ABORTED",
# "action": {
# "app_name": "hi",
# "app_version": "ho",
# "id_": "this_is_an_id",
# "label": "wut",
# "name": "stream_testing",
# "parameters": [],
# "position": {
# "x": 100,
# "y": 100
# },
# "priority": 1
# }}'
+15
View File
@@ -0,0 +1,15 @@
# Get all workflows
curl localhost:5001/api/v1/workflows
# Get A workflow
#curl localhost:5001/api/v1/workflows/a5f82cfd-0f38-3474-20e2-f757f3718707
# NEW workflow
# curl -XPOST localhost:5001/api/v1/workflows -d '{"tags":[],"actions":[],"branches":[],"conditions":[{"label":"Condition","app_name":"Builtin","name":"Condition","conditional":"","id_":"18165f42-aab9-6c9a-0ef4-4e5a37a3b2ad","app_version":"1.0.0","position":{"x":224,"y":168}}],"workflow_variables":[],"name":"asd","start":"18165f42-aab9-6c9a-0ef4-4e5a37a3b2ad","id_":"3d14ca4a-67bd-8dfb-2673-2864f1ccf59c"}'
# Add a workflow
#curl localhost:5001/api/v1/workflows/a5f82cfd-0f38-3474-20e2-f757f3718707 -d '{"actions":[{"app_name":"hello_world","app_version":"1.0.0","errors":[],"id_":"2686a5d4-531d-158f-6b1a-0c1d23481304","is_valid":true,"label":"check_bool","name":"check_bool","parameters":[],"position":{"x":329.98133726556375,"y":160.01013778166904},"priority":3}],"branches":[{"destination_id":"6478ecae-b10e-88e9-e34d-9bbe6aff393d","id_":"5fd6a357-ae33-b1af-5dc2-0306efa28887","source_id":"2686a5d4-531d-158f-6b1a-0c1d23481304"},{"destination_id":"2686a5d4-531d-158f-6b1a-0c1d23481304","id_":"d46d0b05-5757-5084-c339-70ac63985781","source_id":"6478ecae-b10e-88e9-e34d-9bbe6aff393d"}],"conditions":[{"app_name":"Builtin","app_version":"1.0.0","conditional":"","errors":[],"id_":"6478ecae-b10e-88e9-e34d-9bbe6aff393d","is_valid":true,"label":"Condition","name":"Condition","position":{"x":320.97142988802364,"y":394.9753467582139}}],"description":"","errors":[],"id_":"a5f82cfd-0f38-3474-20e2-f757f3718707","is_valid":true,"name":"asd2","start":"2686a5d4-531d-158f-6b1a-0c1d23481304","tags":[],"transforms":[],"triggers":[],"workflow_variables":[]}'
# Execute a workflow
# curl -H "Content-Type: application/json" localhost:5001/api/v1/workflows/3d14ca4a-67bd-8dfb-2673-2864f1ccf59c/execute
#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"}'
+15
View File
@@ -0,0 +1,15 @@
# Build environment
# production environment
from golang as builder
RUN go get github.com/gorilla/handlers
RUN go get github.com/gorilla/mux
WORKDIR /app
COPY webhook.go /app/webhook.go
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o webhook .
from scratch
COPY --from=builder /app/ /
CMD ["./webhook"]
+7
View File
@@ -0,0 +1,7 @@
# Steps to deploy to Google cloud function
1.
```bash
zip webhook.zip *
```
2. Go to google cloud bucket and upload the zip
3. Go to worker for webhook and upload
+44
View File
@@ -0,0 +1,44 @@
package function
import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"time"
)
// GetUserDetails - Get one user's details from randomuser.me API
func GetUserDetails(w http.ResponseWriter, r *http.Request) {
randomUserClient := http.Client{
Timeout: time.Second * 3,
}
req, err := http.NewRequest(http.MethodGet, "https://randomuser.me/api/", nil)
if err != nil {
log.Fatal(err)
return
}
res, err2 := randomUserClient.Do(req)
if err2 != nil {
log.Fatal(err2)
return
}
body, err3 := ioutil.ReadAll(res.Body)
if err3 != nil {
log.Fatal(err3)
}
var o map[string]interface{}
json.Unmarshal([]byte(body), &o)
results := o["results"].([]interface{})
result := results[0].(map[string]interface{})
result["generator"] = "google-cloud-function"
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(result)
}
+4
View File
@@ -0,0 +1,4 @@
docker build . -t gcr.io/shuffle-241517/webhook
docker push gcr.io/shuffle-241517/webhook
gcloud beta run deploy webhook --image gcr.io/shuffle-241517/webhook
+16
View File
@@ -0,0 +1,16 @@
docker stop webhook
docker rm webhook
docker rmi webhook
docker build . -t webhook
docker run -d \
-e "HOOKPORT=5001" \
-e "URIPATH=/webhook" \
-e "CALLBACKURL=http://192.168.159.151:5000/api/v1/hooks/d6ef8912e8bd37776e654cbc14c2629c/result" \
-p 5001:5001 \
--name webhook \
-h webhook \
--restart always \
webhook
docker logs -f webhook
+260
View File
@@ -0,0 +1,260 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
)
type Info struct {
Url string `json:"url" datastore:"url"`
Name string `json:"name" datastore:"name"`
Description string `json:"description" datastore:"description"`
}
// Actions to be done by webhooks etc
// Field is the actual field to use from json
type HookAction struct {
Type string `json:"type" datastore:"type"`
Name string `json:"name" datastore:"name"`
Id string `json:"id" datastore:"id"`
Field string `json:"field" datastore:"field"`
}
type Hook struct {
Id string `json:"id" datastore:"id"`
Info Info `json:"info" datastore:"info"`
Transforms struct{} `json:"transforms" datastore:"transforms"`
Actions []HookAction `json:"actions" datastore:"actions"`
Type string `json:"type" datastore:"type"`
Status string `json:"status" datastore:"status"`
Running bool `json:"running" datastore:"running"`
}
var hook Hook
func handleWorkflowAction(request *http.Request, action HookAction) error {
//log.Printf("WORKFLOW!: %#v", action)
log.Printf("Should execute workflow %s", action.Id)
callbackUrl := os.Getenv("CALLBACKURL")
apikey := os.Getenv("APIKEY")
fullUrl := fmt.Sprintf("%s/api/v1/workflows/%s/execute", callbackUrl, action.Id)
// ret = requests.post(fullurl, headers=headers, json=data)
//if ret.status_code != 202:
// print(ret.text)
// print(ret.status_code)
// print("Exiting workflows - run queue")
// exit()
body, err := ioutil.ReadAll(request.Body)
if err != nil {
return err
}
// Execute a workflow
client := &http.Client{}
req, err := http.NewRequest(
"POST",
fullUrl,
bytes.NewBuffer(body),
)
if err != nil {
log.Printf("Error making http request: %s", req)
return err
}
req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, apikey))
req.Header.Add("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
log.Printf("Error in http request: %s", req)
}
log.Printf("%#v", resp)
return nil
}
// FIXME - refresh hook information once in a while. Compare timestamps or something
func callback(resp http.ResponseWriter, request *http.Request) {
//apikey = os.Getenv("APIKEY")
//hookId = os.Getenv("HOOKID")
handledWorkflowIds := []string{}
for _, item := range hook.Actions {
if item.Type == "" {
log.Printf("CONTINUE AAS EMPTY ITEM: %#v", item)
continue
}
if item.Type == "workflow" {
found := false
for _, workflowId := range handledWorkflowIds {
if item.Id == workflowId {
found = true
break
}
}
if found {
continue
}
handledWorkflowIds = append(handledWorkflowIds, item.Id)
err := handleWorkflowAction(request, item)
if err != nil {
log.Printf("Error in workflow exec: %s", err)
}
}
}
// FIXME - send the webhookdata to a logging service? Idk
//body, err := ioutil.ReadAll(request.Body)
//if err != nil {
// log.Println("Failed reading body")
// resp.WriteHeader(401)
// resp.Write([]byte(fmt.Sprintf(`{"success": false}`)))
// return
//}
//callback, err := http.Post(callbackUrl, "application/json", bytes.NewBuffer(body))
//if err != nil {
// log.Printf("Failed sending callback to %s", callbackUrl)
//}
resp.WriteHeader(200)
resp.Write([]byte(fmt.Sprintf(`{"success": true}`)))
return
}
func loadConfiguration(fullUrl string, apikey string) error {
client := &http.Client{}
req, err := http.NewRequest(
"GET",
fullUrl,
nil,
)
if err != nil {
log.Printf("Error making http request: %s", req)
return err
}
req.Header.Add("Authorization", fmt.Sprintf(`Bearer %s`, apikey))
req.Header.Add("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
log.Printf("Error in http request: %s", req)
return err
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Printf("Error reading response: %s", req)
return err
}
err = json.Unmarshal(body, &hook)
if err != nil {
log.Printf("Failed unmarshaling hook API", req)
return err
}
log.Printf("%#v", hook)
log.Println(hook.Actions)
return nil
}
func webhook() {
// FIXME - remove static
ip := "0.0.0.0"
// Basic webserver stuff
baseFilePath := os.Getenv("URIPATH")
basePort := os.Getenv("HOOKPORT")
callbackUrl := os.Getenv("CALLBACKURL")
apikey := os.Getenv("APIKEY")
hookId := os.Getenv("HOOKID")
if len(baseFilePath) == 0 {
log.Println("Env URIPATH not set")
os.Exit(3)
}
if len(basePort) == 0 {
log.Println("Env HOOKPORT not set")
os.Exit(3)
}
if len(callbackUrl) == 0 {
log.Println("Env CALLBACKURL not set")
os.Exit(3)
}
if len(apikey) == 0 {
log.Println("Env APIKEY not set")
os.Exit(3)
}
if len(hookId) == 0 {
log.Println("Env HOOKID not set")
os.Exit(3)
}
log.Println("Loading hook configuration")
err := loadConfiguration(
fmt.Sprintf("%s/api/v1/hooks/%s", callbackUrl, hookId),
apikey,
)
if err != nil {
log.Fatalf("Error loading config: %s", err)
}
// Optional
// if len(callbackOpts) == 0 {
// log.Println("Env CALLBACKOPTS not set")
// os.Exit(3)
// }
port := fmt.Sprintf(":%s", basePort)
log.Printf("Starting webhook on %s%s with path %s", ip, port, baseFilePath)
// Routing
mux := mux.NewRouter()
mux.SkipClean(true)
// FIXME - Add path for updating the hook? Can be a specific POST requeuest from backend
mux.HandleFunc(baseFilePath, callback).Methods("POST")
handlers.LoggingHandler(os.Stdout, mux)
loggedRouter := handlers.LoggingHandler(os.Stdout, mux)
err = http.ListenAndServe(
port,
loggedRouter,
)
if err != nil {
log.Fatal("ListenAndServer: ", err)
}
}
func F(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.Write([]byte(r.Header.Get("X-Forwarded-For")))
}
func main() {
webhook()
}
+68
View File
@@ -0,0 +1,68 @@
version: '3'
# Remove database port
# Remove backend port
services:
frontend:
build: ./frontend
image: frikky/shuffle:frontend
container_name: shuffle-frontend
hostname: shuffle-frontend
ports:
- "3001:80"
networks:
- shuffle
restart: unless-stopped
database:
build: ./backend/database
image: frikky/shuffle:database
container_name: shuffle-database
hostname: shuffle-database
ports:
- "8000:8000"
networks:
- shuffle
restart: unless-stopped
volumes:
- /etc/shuffle:/etc/shuffle
backend:
build: ./backend
image: frikky/shuffle:backend
container_name: shuffle-backend
hostname: ${BACKEND_HOSTNAME}
ports:
- "${BACKEND_PORT}:${BACKEND_PORT}"
networks:
- shuffle
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- ORG_ID=${ORG_ID}
- DATASTORE_EMULATOR_HOST=shuffle-database:8000
- BACKEND_PORT=${BACKEND_PORT}
restart: unless-stopped
depends_on:
- database
- frontend
orborus:
build: ./functions/onprem/orborus
image: frikky/shuffle:orborus
container_name: shuffle-orborus
hostname: shuffle-orborus
networks:
- shuffle
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- ORG_ID=${ORG_ID}
- ENVIRONMENT_NAME=${ENVIRONMENT_NAME}
- BASE_URL=http://${OUTER_HOSTNAME}:${BACKEND_PORT}
restart: unless-stopped
app_sdk:
build: ./functions/onprem/app_sdk
image: frikky/shuffle:app_sdk
worker:
build: ./functions/onprem/worker
image: frikky/shuffle:worker
networks:
shuffle:
driver: bridge
+33
View File
@@ -0,0 +1,33 @@
# Build environment
FROM node as builder
RUN mkdir /usr/src/app
WORKDIR /usr/src/app
ENV PATH /usr/src/app/node_modules/.bin:$PATH
COPY package.json /usr/src/app/package.json
RUN npm install --verbose
COPY . /usr/src/app
RUN npm run-script build
# Production environment
from nginx:latest
RUN mkdir -p /usr/share/nginx/html/build
RUN mkdir -p /usr/share/nginx/html/css
RUN mkdir -p /usr/share/nginx/html/js
RUN mkdir -p /usr/share/nginx/html/img
COPY --from=builder /usr/src/app/build /usr/share/nginx/html
COPY --from=builder /usr/src/app/certs/fullchain.pem /etc/nginx/fullchain.cert.pem
COPY --from=builder /usr/src/app/certs/privkey.pem /etc/nginx/privkey.pem
# Test
COPY --from=builder /usr/src/app/nginxtest.conf /etc/nginx/nginx.conf
# Prod
#COPY --from=builder /usr/src/app/nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
EXPOSE 443
+3
View File
@@ -0,0 +1,3 @@
npm run build
rm -rf ../backend/go-app/build
cp -r build/ ../backend/go-app/build
+10
View File
@@ -0,0 +1,10 @@
This directory contains your keys and certificates.
`privkey.pem` : the private key for your certificate.
`fullchain.pem`: the certificate file used in most server software.
`chain.pem` : used for OCSP stapling in Nginx >=1.3.7.
`cert.pem` : will break many server configurations, and should not be used
without reading further documentation (see link below).
We recommend not moving these files. For more information, see the Certbot
User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates.
+31
View File
@@ -0,0 +1,31 @@
-----BEGIN CERTIFICATE-----
MIIFRzCCBC+gAwIBAgISA2OOAaaFtVzoqzLXCBV19fpjMA0GCSqGSIb3DQEBCwUA
MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xOTAyMDkxMjQxMzlaFw0x
OTA1MTAxMjQxMzlaMBMxETAPBgNVBAMTCGVuZGFvLmlvMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAr4nj/G0MxSEWHnD0oC+jS03ofE6hXC87z0h6iNh5
lRdVoh9o2H22iQNTmWOWB56oqdBQEOeCcNFexYOzL9S91IVy3Q+hDev5rkJ44uFP
0VtgKbTc/aKIpEcSYPda2b1lb1mAYpJ/HqvzMDrJ+izCpTLEs2h+mD+2LAx7Ie3Z
OE+mFhU7aYdJ7rbUob7BT9cxzbp4CLGjaZHFKrC1uafSSh3bFFlXJpYmleunGVS9
4BUuveZdWJGLFMN1cAJiMGLewsKp5YXKB9eiH6gAg90udRaanPz3NgFa/8qju9iA
64YJcDNr9Aj04EIntRbPJm286+TrJbaf57ESCS32cqaDEwIDAQABo4ICXDCCAlgw
DgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAM
BgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTaQOYhr73PSHSd+P+b7FJhYcVPwDAfBgNV
HSMEGDAWgBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcBAQRjMGEwLgYI
KwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcwLwYI
KwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcvMBMG
A1UdEQQMMAqCCGVuZGFvLmlvMEwGA1UdIARFMEMwCAYGZ4EMAQIBMDcGCysGAQQB
gt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5cHQub3Jn
MIIBAwYKKwYBBAHWeQIEAgSB9ASB8QDvAHYA4mlLribo6UAJ6IYbtjuD1D7n/nSI
+6SPKJMBnd3x2/4AAAFo0n3+fgAABAMARzBFAiBebCfZn5HQoIloL8iS7Q2U9DE/
IlMLvhK0UFlhr1u5agIhAN84nfBT1slMt86E95tvvYbEHcAfapogmJmOxSoI5ati
AHUAY/Lbzeg7zCzPC3KEJ1drM6SNYXePvXWmOLHHaFRL2I0AAAFo0n4AegAABAMA
RjBEAiARET8ZHf3qcahgGa9O440U7/Zbfi5+WO0XRlN/cVQZBgIgKQpHRXv9urpk
MS+tz9jvsxERUYEEjRgsSnM1Bs3AF44wDQYJKoZIhvcNAQELBQADggEBADPUIj7w
z+He68As1CFu9tAwGMeLZDgXtPsybAOY9cWm9aL5sC5RldK+ob/5Xi2aa7YEeeij
VP44lOjXtr3EmGm34vN9pL7JyAhYAmEZToyJEt4dbyCbLhO0IT9Po4ropiXk7ze0
CRNEg2CCdXkG7KLHIxP6Am1vr1/TA8IC4lf0ocYJs5sU4/7Bj8eViuZ7F8ZcJo1t
O24jBw5m48fbmmDWkcc3YHVD55sIys3RDEQMLABgIxgN+kqRqfCslREpPYWa/o2T
jOt99S/zjdeRgGJfO8XKtI2TH8ZMHNyKaJYjmdX0CcZFmm2E8SZRYuB4j/KUdd8a
xje2fid5pAJf2Tk=
-----END CERTIFICATE-----
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
-----END CERTIFICATE-----
+58
View File
@@ -0,0 +1,58 @@
-----BEGIN CERTIFICATE-----
MIIFRzCCBC+gAwIBAgISA2OOAaaFtVzoqzLXCBV19fpjMA0GCSqGSIb3DQEBCwUA
MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xOTAyMDkxMjQxMzlaFw0x
OTA1MTAxMjQxMzlaMBMxETAPBgNVBAMTCGVuZGFvLmlvMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAr4nj/G0MxSEWHnD0oC+jS03ofE6hXC87z0h6iNh5
lRdVoh9o2H22iQNTmWOWB56oqdBQEOeCcNFexYOzL9S91IVy3Q+hDev5rkJ44uFP
0VtgKbTc/aKIpEcSYPda2b1lb1mAYpJ/HqvzMDrJ+izCpTLEs2h+mD+2LAx7Ie3Z
OE+mFhU7aYdJ7rbUob7BT9cxzbp4CLGjaZHFKrC1uafSSh3bFFlXJpYmleunGVS9
4BUuveZdWJGLFMN1cAJiMGLewsKp5YXKB9eiH6gAg90udRaanPz3NgFa/8qju9iA
64YJcDNr9Aj04EIntRbPJm286+TrJbaf57ESCS32cqaDEwIDAQABo4ICXDCCAlgw
DgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAM
BgNVHRMBAf8EAjAAMB0GA1UdDgQWBBTaQOYhr73PSHSd+P+b7FJhYcVPwDAfBgNV
HSMEGDAWgBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcBAQRjMGEwLgYI
KwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcwLwYI
KwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcvMBMG
A1UdEQQMMAqCCGVuZGFvLmlvMEwGA1UdIARFMEMwCAYGZ4EMAQIBMDcGCysGAQQB
gt8TAQEBMCgwJgYIKwYBBQUHAgEWGmh0dHA6Ly9jcHMubGV0c2VuY3J5cHQub3Jn
MIIBAwYKKwYBBAHWeQIEAgSB9ASB8QDvAHYA4mlLribo6UAJ6IYbtjuD1D7n/nSI
+6SPKJMBnd3x2/4AAAFo0n3+fgAABAMARzBFAiBebCfZn5HQoIloL8iS7Q2U9DE/
IlMLvhK0UFlhr1u5agIhAN84nfBT1slMt86E95tvvYbEHcAfapogmJmOxSoI5ati
AHUAY/Lbzeg7zCzPC3KEJ1drM6SNYXePvXWmOLHHaFRL2I0AAAFo0n4AegAABAMA
RjBEAiARET8ZHf3qcahgGa9O440U7/Zbfi5+WO0XRlN/cVQZBgIgKQpHRXv9urpk
MS+tz9jvsxERUYEEjRgsSnM1Bs3AF44wDQYJKoZIhvcNAQELBQADggEBADPUIj7w
z+He68As1CFu9tAwGMeLZDgXtPsybAOY9cWm9aL5sC5RldK+ob/5Xi2aa7YEeeij
VP44lOjXtr3EmGm34vN9pL7JyAhYAmEZToyJEt4dbyCbLhO0IT9Po4ropiXk7ze0
CRNEg2CCdXkG7KLHIxP6Am1vr1/TA8IC4lf0ocYJs5sU4/7Bj8eViuZ7F8ZcJo1t
O24jBw5m48fbmmDWkcc3YHVD55sIys3RDEQMLABgIxgN+kqRqfCslREpPYWa/o2T
jOt99S/zjdeRgGJfO8XKtI2TH8ZMHNyKaJYjmdX0CcZFmm2E8SZRYuB4j/KUdd8a
xje2fid5pAJf2Tk=
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
-----END CERTIFICATE-----
+34
View File
@@ -0,0 +1,34 @@
-----BEGIN CERTIFICATE-----
MIIF+TCCBOGgAwIBAgISA8rwfiC3p89WYm/X9RR3XhzTMA0GCSqGSIb3DQEBCwUA
MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xODExMDkyMTEzNTNaFw0x
OTAyMDcyMTEzNTNaMBMxETAPBgNVBAMTCGVuZGFvLmlvMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA2NIGtVlzW3fktmv74YG23LKLM6xIxn/mIPt4PgZ+
K3407GryQueToAnG5iTXTox5UnyArvqxAe5cTonrlWTai9Do5CuhrKI22EMpwrSl
Uo38dtNu2AHLENrY+aFzEwsVvkWGpIQ8/Y63T8M9ohiInF/S5ZPojIJxy7OVQ3io
MdLbb1atYfBcnt0DFKyMC1yxDyYj8w1jvwQWlKGBYQeRd2MTWEjHO7CJPgiRwiLi
rFBlDWnFz8GKntXLHI2pYL6+X4WgG9XfwzSN/w+ju48JAgCc6yTxvaQxQDpHst3D
kv4CXNTP3gt2RSIOkQldBQzi/FgBLDv/qSuXAnLGsIgk5QIDAQABo4IDDjCCAwow
DgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAM
BgNVHRMBAf8EAjAAMB0GA1UdDgQWBBThSBGBk0ZSYT1ptylvWQGZqdkBDjAfBgNV
HSMEGDAWgBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcBAQRjMGEwLgYI
KwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcwLwYI
KwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcvMBMG
A1UdEQQMMAqCCGVuZGFvLmlvMIH+BgNVHSAEgfYwgfMwCAYGZ4EMAQIBMIHmBgsr
BgEEAYLfEwEBATCB1jAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5sZXRzZW5jcnlw
dC5vcmcwgasGCCsGAQUFBwICMIGeDIGbVGhpcyBDZXJ0aWZpY2F0ZSBtYXkgb25s
eSBiZSByZWxpZWQgdXBvbiBieSBSZWx5aW5nIFBhcnRpZXMgYW5kIG9ubHkgaW4g
YWNjb3JkYW5jZSB3aXRoIHRoZSBDZXJ0aWZpY2F0ZSBQb2xpY3kgZm91bmQgYXQg
aHR0cHM6Ly9sZXRzZW5jcnlwdC5vcmcvcmVwb3NpdG9yeS8wggECBgorBgEEAdZ5
AgQCBIHzBIHwAO4AdQB0ftqDMa0zEJEhnM4lT0Jwwr/9XkIgCMY3NXnmEHvMVgAA
AWb6ieWoAAAEAwBGMEQCIE8+auAtQOLY4U1yoEDvCTQULv4PS9Xdo1uJzQF5nhBA
AiBsfKR2UMxxIatzPOQPtuRrVEAv4+tX4o/ch5e6TAnOCAB1ACk8UZZUyDlluqpQ
/FgH1Ldvv1h6KXLcpMMM9OVFR/R4AAABZvqJ6BEAAAQDAEYwRAIgKj2gwlC/9AIF
hrx8nl2oEwxUoi/b4ZCCdQpz8JsJhOACIHRkuo56NErZR87lVtI0oZItZ6nzFPQz
BFaxx89eBJKPMA0GCSqGSIb3DQEBCwUAA4IBAQCFURbxUFKAF3CJRiaUsRrjFDuc
R25Dm86QgDqDLGkYf7/y5by2/amNzBbBLvcqUhScpjtalqO38Efw+2+c/UBGTQZ7
C8vU5KrjG6bnOmoHx/t+ml4E88omMlEmlRIEiL/6yfpOnCCv+3IHJjUw8iPuH7+j
57Vhwtq6mOvId2QfDfL86ELgXKvBKfbqac7YfzUHVyB4WeXagfpYiObpspTud0tZ
Yf5Qs7g8GLkXfpkOJHG1+91Hi7+Pb1cCAqkkgIwFG4oeBdxAkxkaIyBRk3ZfnXYw
9G1qofLrs2UbLUhIEyMgavI7kKwLg1pwsYs4Yn+RVR5hZPkj0ZUnc4gW7wCP
-----END CERTIFICATE-----
+12
View File
@@ -0,0 +1,12 @@
Traceback (most recent call last):
File "/usr/bin/certbot", line 11, in <module>
load_entry_point('certbot==0.10.2', 'console_scripts', 'certbot')()
File "/usr/lib/python2.7/dist-packages/certbot/main.py", line 836, in main
setup_logging(config)
File "/usr/lib/python2.7/dist-packages/certbot/main.py", line 700, in setup_logging
config, logfile=logfile, fmt=file_fmt)
File "/usr/lib/python2.7/dist-packages/certbot/main.py", line 667, in setup_log_file_handler
raise errors.Error(_PERM_ERR_FMT.format(error))
Error: The following error was encountered:
[Errno 13] Permission denied: '/var/log/letsencrypt/letsencrypt.log'
If running as non-root, set --config-dir, --logs-dir, and --work-dir to writeable paths.
+27
View File
@@ -0,0 +1,27 @@
-----BEGIN CERTIFICATE-----
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
-----END CERTIFICATE-----
+61
View File
@@ -0,0 +1,61 @@
-----BEGIN CERTIFICATE-----
MIIF+TCCBOGgAwIBAgISA8rwfiC3p89WYm/X9RR3XhzTMA0GCSqGSIb3DQEBCwUA
MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xODExMDkyMTEzNTNaFw0x
OTAyMDcyMTEzNTNaMBMxETAPBgNVBAMTCGVuZGFvLmlvMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEA2NIGtVlzW3fktmv74YG23LKLM6xIxn/mIPt4PgZ+
K3407GryQueToAnG5iTXTox5UnyArvqxAe5cTonrlWTai9Do5CuhrKI22EMpwrSl
Uo38dtNu2AHLENrY+aFzEwsVvkWGpIQ8/Y63T8M9ohiInF/S5ZPojIJxy7OVQ3io
MdLbb1atYfBcnt0DFKyMC1yxDyYj8w1jvwQWlKGBYQeRd2MTWEjHO7CJPgiRwiLi
rFBlDWnFz8GKntXLHI2pYL6+X4WgG9XfwzSN/w+ju48JAgCc6yTxvaQxQDpHst3D
kv4CXNTP3gt2RSIOkQldBQzi/FgBLDv/qSuXAnLGsIgk5QIDAQABo4IDDjCCAwow
DgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAM
BgNVHRMBAf8EAjAAMB0GA1UdDgQWBBThSBGBk0ZSYT1ptylvWQGZqdkBDjAfBgNV
HSMEGDAWgBSoSmpjBH3duubRObemRWXv86jsoTBvBggrBgEFBQcBAQRjMGEwLgYI
KwYBBQUHMAGGImh0dHA6Ly9vY3NwLmludC14My5sZXRzZW5jcnlwdC5vcmcwLwYI
KwYBBQUHMAKGI2h0dHA6Ly9jZXJ0LmludC14My5sZXRzZW5jcnlwdC5vcmcvMBMG
A1UdEQQMMAqCCGVuZGFvLmlvMIH+BgNVHSAEgfYwgfMwCAYGZ4EMAQIBMIHmBgsr
BgEEAYLfEwEBATCB1jAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5sZXRzZW5jcnlw
dC5vcmcwgasGCCsGAQUFBwICMIGeDIGbVGhpcyBDZXJ0aWZpY2F0ZSBtYXkgb25s
eSBiZSByZWxpZWQgdXBvbiBieSBSZWx5aW5nIFBhcnRpZXMgYW5kIG9ubHkgaW4g
YWNjb3JkYW5jZSB3aXRoIHRoZSBDZXJ0aWZpY2F0ZSBQb2xpY3kgZm91bmQgYXQg
aHR0cHM6Ly9sZXRzZW5jcnlwdC5vcmcvcmVwb3NpdG9yeS8wggECBgorBgEEAdZ5
AgQCBIHzBIHwAO4AdQB0ftqDMa0zEJEhnM4lT0Jwwr/9XkIgCMY3NXnmEHvMVgAA
AWb6ieWoAAAEAwBGMEQCIE8+auAtQOLY4U1yoEDvCTQULv4PS9Xdo1uJzQF5nhBA
AiBsfKR2UMxxIatzPOQPtuRrVEAv4+tX4o/ch5e6TAnOCAB1ACk8UZZUyDlluqpQ
/FgH1Ldvv1h6KXLcpMMM9OVFR/R4AAABZvqJ6BEAAAQDAEYwRAIgKj2gwlC/9AIF
hrx8nl2oEwxUoi/b4ZCCdQpz8JsJhOACIHRkuo56NErZR87lVtI0oZItZ6nzFPQz
BFaxx89eBJKPMA0GCSqGSIb3DQEBCwUAA4IBAQCFURbxUFKAF3CJRiaUsRrjFDuc
R25Dm86QgDqDLGkYf7/y5by2/amNzBbBLvcqUhScpjtalqO38Efw+2+c/UBGTQZ7
C8vU5KrjG6bnOmoHx/t+ml4E88omMlEmlRIEiL/6yfpOnCCv+3IHJjUw8iPuH7+j
57Vhwtq6mOvId2QfDfL86ELgXKvBKfbqac7YfzUHVyB4WeXagfpYiObpspTud0tZ
Yf5Qs7g8GLkXfpkOJHG1+91Hi7+Pb1cCAqkkgIwFG4oeBdxAkxkaIyBRk3ZfnXYw
9G1qofLrs2UbLUhIEyMgavI7kKwLg1pwsYs4Yn+RVR5hZPkj0ZUnc4gW7wCP
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
-----END CERTIFICATE-----
+28
View File
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDY0ga1WXNbd+S2
a/vhgbbcsoszrEjGf+Yg+3g+Bn4rfjTsavJC55OgCcbmJNdOjHlSfICu+rEB7lxO
ieuVZNqL0OjkK6GsojbYQynCtKVSjfx2027YAcsQ2tj5oXMTCxW+RYakhDz9jrdP
wz2iGIicX9Llk+iMgnHLs5VDeKgx0ttvVq1h8Fye3QMUrIwLXLEPJiPzDWO/BBaU
oYFhB5F3YxNYSMc7sIk+CJHCIuKsUGUNacXPwYqe1cscjalgvr5fhaAb1d/DNI3/
D6O7jwkCAJzrJPG9pDFAOkey3cOS/gJc1M/eC3ZFIg6RCV0FDOL8WAEsO/+pK5cC
csawiCTlAgMBAAECggEALGoxp5qQT+9qcZgDO/mnbORCMa6cJdVzXdRFmGcaF2y+
CKQLxnrLud/m16Q6WsPZ4nTQo4eFFQEv9YK5OJw1TKjZ3Eu3zbZZB8oSVulMaHHf
grPjI+qSH3zFL7XL7d26cYSqhS71k3dVw8gZ6wedjHLwr8ixvX7HMtQxwmWIBtRr
Sj9zYyqkoBLDWrhoGh7GlAHHiPw4cFY7XRaAbq36JYhkO+VUK7BRJMMPwSSMbmx1
S3O8PhXy5he893HVRAHhQq8xIup/Q+PktB39xgQX0M+mjMoqD9mJ0fgaKnff8K3U
C3ovU9YmAR9VTGE/1S4yeT5BreXGbCwXKGSHO4KBLQKBgQDtZWjbtUCHyuiNk8pp
17mgpWEZOHl7+hehO6uN2GwE4bdrBRxK3b+ZQRJqVRQnhQRkYA4x1vVOG05A0b2C
QJBERd66XWMZHs3820kexo+Lw0XgAaM5bIoW8VZxAhHL9J49piiboawp/sNlFPVl
HL0x5ztkP/7QHRwvInY9giVNAwKBgQDpz9S7N/wNwQf6NrqbboYpUkA8WcAjGdzi
gsgK47rm2Nvs5CTWgVykTVokN/HjUCnkzNcET2e0lwF3M2Z6f3wyWiwngrGCWVyL
YXgcMT080CPWvfpVZ/kjkOORsD4zhceVs/ECJDPwNH7rKderILxQld7BQ+PcY32n
5iu4L3Cd9wKBgQDIUWsjAhX6v+BuHwiNOYicoxCFHJ9+WvF3jwdbAQVdNS31s3FF
R0q2wi8M5M/F7Ttgi6FOswl5qBbnIVTdRSe3RJIGBmlpuBGvTUatHnXgRJ2AwUD/
YrQ/WdRMNafYx4iDkuLvOIQzR5OPhxjvAkovguQd2tZkvDudFmJZ0qkxCwKBgAKK
R31hNlPP7Q5C2fQmVz7Lrfb54qR++29ajGHR543qfTktoVjTPvZqALi0AuS9Ujcy
IoPhePb1TQbGgC6EhAzn/eLQAK2x2teIz40+27N1b2490iJrZsGEKXTDvWlB4tE1
i0DCs+3AJEDZy4YIbQNTHYBsBGW4jvzr+z2dBlQxAoGAZaQ8pL6wnaXqCQA/ULO+
lcF5c98pOLHpNTE19zodDEpG1OmavHgDhThOXJYfogkeAJaoovaQ5hHS/YLkXVOU
Xd2JN5FLbAwLN1jorB5tH1+c2SOOXaGoi7BTM9/mcUkJqYIthK4SoYw0oO2nphGZ
Mm8KDWDzu7D3r3XB8Xpl8ng=
-----END PRIVATE KEY-----
+28
View File
@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCvieP8bQzFIRYe
cPSgL6NLTeh8TqFcLzvPSHqI2HmVF1WiH2jYfbaJA1OZY5YHnqip0FAQ54Jw0V7F
g7Mv1L3UhXLdD6EN6/muQnji4U/RW2AptNz9ooikRxJg91rZvWVvWYBikn8eq/Mw
Osn6LMKlMsSzaH6YP7YsDHsh7dk4T6YWFTtph0nuttShvsFP1zHNungIsaNpkcUq
sLW5p9JKHdsUWVcmliaV66cZVL3gFS695l1YkYsUw3VwAmIwYt7CwqnlhcoH16If
qACD3S51Fpqc/Pc2AVr/yqO72IDrhglwM2v0CPTgQie1Fs8mbbzr5Osltp/nsRIJ
LfZypoMTAgMBAAECggEAVrRic0WeACTWmxSqEBFXtBN4WSYxl6oQ/dLMC5n5fAX5
m0mubPizV7vX6DUDXyIkJzSox6DCkl0oVaE2udJ4LWf5E23r3EeJnWRh95bY6Q0c
U51tZq6IlbQxRMoJCvH9D2IDAy1fMtQ2G8V+DF9diWGpPQDJPwSBRAKqM1kbaS9E
40zoKzzkYWQYHnNrgiLnUpT6LxVJrKQo4B/HxhoovlQ+Uu8oRbK+JdRvKTWQ7ELV
evRtXxHSeq+22TdTysDfalOVWudaUSe2CqzX90mGzxerA7Baorb0Fas9nhTPFZus
pqOYJPDUSU0SPj939lArlsAHCpUY2/NOK6tOeotQ0QKBgQDX7I/GmqiN4BXnmlBC
fvix1BSQNs0d1fVMWiVXhgjUnTWc7oyEnE5tDkS6OyUKFHiK+xYxhy9rjis8ch+h
CfyafADku6y3lG2QwktDA48cRavzmRZhwbbDqsRLIKRUqJyon8Xn4jsX0QTTHM75
jJPSFLny0nsvIbQgixz7D0FrmQKBgQDQHnOIRvcYYGWV9kxGw+l9I+JNnX8RZWmD
u9fZnqUQ01NtR6DHhmes73GtvRmhEeEQgICwpmBY65cUYHYkQ6WmhxdPGrSxLLCn
539nvAhVs5WgFbEGsV7alFob1A2kbMWxnmvtk/mmaXyFf1D7jgDn9mabtsXlUVzk
2OW+/mAviwKBgH0LGSQ5sn0fFXBflDU+FMXe/N2bbuPlYT4LQm7SR1DhjgfIugWe
A2jyh2iWEdgpjLlnuS2LjTGrTmyd5qU7BFdukpHkAZz6zwyzCCTR7dHAo1jaAfUw
2qerwCuFxsTkNH0Oseycvf4H5NRhbZdlCsHxkNIHhKEB0q+6AFmANzGZAoGBAK5r
Zu4mc34Fg/3Lfp+sH2oWgdoEjfL5aBXUfxFOaQ6R3ZBvgGrX1NnVjxNJsUoRXuti
lvf1aDf9sg+MzFaWLyVCgKF2Q0tdpewCEv/QUbfpmxOgOk3epx1PSbma3ZgY5RJ4
MAHbi1YRgB+t1SQOHepJ0jLcWjxXFSBiOyH3tIsDAoGANQ6zOvNV/Mjkj82AJZGa
bW21TcMWBZhCJZc7jI93S9oCJl+qp9eHjUVbsg6pf+8MWOmMtXFLgC/idJF8tB1E
OzsW2xoKKSstj8JdAPsx8vR2kkLfkXPMT6ymOA7Lbc4ZwKSDbwo5xdAR8qBAnkic
xIKG1OZD55i9MuHng2uBJBc=
-----END PRIVATE KEY-----

Some files were not shown because too many files have changed in this diff Show More