Initial open source commit
This commit is contained in:
@@ -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
|
||||
@@ -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
@@ -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)
|
||||
@@ -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
|
||||
@@ -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,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)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
# Generators
|
||||
This folder contains an attempt at creating apps & similar from python libraries
|
||||
|
||||
## Howto
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
@@ -0,0 +1,2 @@
|
||||
jedi
|
||||
pyyaml
|
||||
Reference in New Issue
Block a user