Skip to content

Commit f4297dc

Browse files
committed
Merge branch 'master' of https://github.com/shuffle/python-apps
2 parents e98c46c + 31f8d79 commit f4297dc

File tree

9 files changed

+791
-26
lines changed

9 files changed

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

shuffle-tools-fork/1.0.0/api.yaml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
app_version: 1.0.0
3+
name: Shuffle Tools Fork
4+
description: A tool app for Shuffle. Gives access to most missing features along with Liquid.
5+
tags:
6+
- Testing
7+
- Shuffle
8+
categories:
9+
- Other
10+
contact_info:
11+
name: "@frikkylikeme"
12+
url: https://shuffler.io
13+
14+
actions:
15+
- name: execute_python
16+
description: Runs python with the data input. Any prints will be returned.
17+
parameters:
18+
- name: code
19+
description: The code to run. Can be a file ID from within Shuffle.
20+
required: true
21+
multiline: true
22+
example: print("hello world")
23+
schema:
24+
type: string
25+
- name: packages
26+
description: The code to run. Can be a file ID from within Shuffle.
27+
required: true
28+
multiline: true
29+
example: pandas\nnumpy\nmatplotlib
30+
schema:
31+
type: string
32+
33+
large_image: data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAK4AAACuCAYAAACvDDbuAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAAB3RJTUUH5AgXDjM6hEZGWwAAD+lJREFUeNrtXb/vJTcRH7/v3iVBCqRBiCAQAtHwq4AWRElHwX8AoqbmXwDRpiH/QyQkGoogUSAhKIKUAE1IdSRSREhQQk7c3XtD8X55vePxjNfe3bk3H+nu+96uPf54POtnj8fe8OQX30JwOIxhtzYBh6MGOsPF0z9p2iWwpd8LjX6W5vWUYaiqlBuvLT5b5TQDPlRwmMSAABBg+kCer+XuAeQf4tL9tAxJ/hIfZGSm8rhyEfjytfxr9FeSX+KjvVfipNVpWlaPNhsAEPCS7Ao8FYnRlbO4ksLnjiSQvIanv4FNjwJ5pXIlMq6MQpIqqPnQKQKbjuPDtZlG55o6UHXWtVncZZTbbNBVB1P5dJYguCbJJ1WjOG8PVOioSm5HPrVt1rwuyN+K+PSZnNV1M/MmEFubfFjjU9tmK9XBJ2cOk3DDdZiEG67DJOrGuA7HyvAe12ESAxa73KPrN1z8gUikCCdvcD5NXnpQpA8nNhh9m5Yn4ZMrV8dHV/8a/dRA0x419a3lI9GBtM2GcrGYFXRNUU5TyluTOpdXwqeUt6YOpby9DUTLZylOcRlzdBTf2yV3ZBFOmKSHQh5KpjSSSpqG4s6VkUubqw8W8knTSnWk0Y+2jF5tlmuDUloJn6T8gRVcEpJ+3srChHSNt8RJsq4p+S41LC13KTcu/RJt1pLPKY1Pzhwm4YbrMAk3XIdJTMe4aeCjJhBVk0YiQ1MWZHhLgmO5QNVWfKRlavlIIQnurQmcnaMjSbBxhtMwYUxODpLcl2tUhvPlNE6VkiuoFVLXKT6ZfBjxRIIzOSlgWpLSB8uZ0g3BjeVDlFGEos0mfKKL7CQrY2ES7pM2i/OX22w4/sWReEhEnUOTxx3a+FrawQGZh04/rWe6oJBKo5zT4zLjPHE9ZHym5YzToogzfQcmfLgOhuLF/Sjm2izVDyXnrKtcmmmdaKumf+RyCw5Xn7OmzQaJF0fiEZG6BjXpYUYaSVkaPrXeHe4eVaZEr3Prqrmmrbc2T8lrmOMjn5xJHeJLYkk+PfzNTxOflrwF0EeHbU0Zt2wsW+PTkncB7g5zmMSwzUfS4eDhPa7DJK5jXGorsnZxonbRIbeAoOUjkUvlp+qxFp9YNuWL0nBqsVCkqUsrHQnuX+Nx5/qcJDI0kWgtJh7ihYCN8aG+13DqOXlbWUfD+fN0AUEmp3RcUWlVEwCynb5ssYLnxHViJT6ULCykb8EnzUfpqBWfVAdcnt5tprGhIe10WnjHpB2FtMPWcpM66yXyOad4Lz4Srq34SHhwZfRos1w9Y/jkzGESvj3dYRLe4zpMwg3XYRJuuA6T4M/Hzfk/OGd9OP2HOE2f8wtBlCebJrkfp+Gc3AGmiSiuaVlpwkmajL4osPUm9FMqIzBOJolfjGuzEtdUwWl53Dm7Eh9pzIdps+FiYJyi1N+Rvs/6OLCQBul8Ip8R08ik3EwhLZz1Wv8XmU7ZZqX7OT2gUIB2oaRBm+2ovDm5nM+ulEeiD8yka8UnJ1PCP82r9YWW8iCU5XO8W/PhPmvllNKW7lEyszsgNKuzkspJFZFL15uPtIweq7A1xiKpz1J8tGXP+dE53/fJmcMk6hcgJO8XqokEKi5uYzTG29LqSev95JqyKsoOOxjNpKQBD7VFc5GBJRsi+NQHkkv6+7m/UxTufwLCCy+CbAruyOLDdwEf/uf6vbbNJukzlogZC6wMdhAcM7ohHPawe/GrcO+HPwe4u782G7sIAE9++0vYv/YKwO6usfCaka0etgwXAGB3D8JznwIYnlmbiW0M92FbQy0d+MmZ3Xo5JDDcvuXJ2ZYqtyUuTwuM6nSXctcufHCOZqkjPScXhbIcdeD0XUpfKyNNy8nlyhuozLkM8XxR6pjm7tc4Fdx620I7lWq10JCm0ZanWoBwm3FsBe1WznpadbTg4A9PI2xx7FUKHopQjg7TKqNnpbioIUcFUGUsy1CS8fFYBYdJuOE6TMIN12ESgyiKiwO1bQOJe1w+6p42Etmhwmi6kLZXfC2G9IUj2vulY2wIPrv4onRhIXcRqS0DiWxkhF0uIb37wG22LRCSuVCyekC2GSXj9CG3YyT+krWh+KPAhkTvgGDKqbqnWbBwY+2Pnm3Wy4aMRYc1MuPDvp0skwgAh8PaJGbh5k4kx0f/hce/ewnw/QenXQCTFJDfQy45PzFNn5NHsoPy/u6gzE+nObzz91P9Z+6kWAm2zg6bDMoq8OQxHN78Axze/htAaB1EbQhhdzyfgRqIGoCxoUIjhDuA3ZDpcR0W4C3nMInbNVw7v4oOAsehArVFPL0uOjMM+DlM+pk7t7/BDuwcJsM6gcM7WweOX05nFCHNi12ASRfLo3QaX9O0GWTylOTnZIMwf4YPPTlD4iMm7aZwAGOUf3Rf48wjHNzVOMkKFA8pp0RHZ1mjdihs5R61PWbsWlphgs/E5gptNvFfSLY8QPk7dVbh+UNg8qfnJsZ8Bo0hzF0Y2Nqvc0s+Vbs5YL5OLfPRcorT2hvjtuxyHWZhzHCX6AMcFtB2B0RvtKZqqe6OEYz1uA7HEbdruN7ZmsZtGq4brXnQhlsbLFkDrY9mC9giH41/dSlONfeEIBcgss7nXopInPdkYN95J3XD1bMgkJUNFOxsDNLgyiynhYyX5dnAhnLyhzmO4V7IO8+xyZEgx5UqvJ41rOUTdhBOr2w6KjZc+B1FBkLGVUoAABQEcmPu6rPPw73v/gh2n/wMANYEhAd4/NqvYf/Wn5pEyPW2IUrOzQWSHyHdkEJgN8D97/0Edp/7GgDu9fnDDvD9t+HRqy8BPvxQ9i6xEXUEuPcMDF//Puw+/aVqDewfvA77f/zx9M40e7jNeNw5CDu4++K34e4r36kWcXj3TYDfvwz8D79ml1clDPuxx9FhuUik0rblVihFWLX+7ZFEXE2ioLBNg9fUSRopVsOjJbioskZlDuyAvmflpOWsOUNu/cBQ8jW/1A0np11RG+GjwG36cQHqFWnBcG4Axgx37d/I1uXXcvCnx6BXoQXf3mOAzvVpooJzaOcWdKBH1fZ07dCsFZpNgmfZbaOJ2dxnpwkNFC3C9MBcGxo0OugxwV8LWKm5lg9sFQdszKGhLAla2dCuduuOZcypx+UXdk0OK5e/hXKNTc4cjiPGhtvTX1njI6Z2+vbuKtaKspLooXdkXs1u5yUR7/LdROMsraSSIfTa6pqWodE9Mvla6sCI8d7uUMEXIEzjdg3XYRr2osOePIbDR+9BGO7re78QAD/+AODwpK5sBDg6dGyGAtL1sYnLGDe3+2BNTNycYQf7B2/Aw5d/XB9HejjA4YN3jgHUNQ132MOTv/wG9v98A+CgFBCO/+FH/wJ89PBaSY1OULZzQyQL2skayVwg/7Dk3Ky2IlcEgEcfw/7dt+YJnRP1f9jDoz+/AvM0FU4c1u8mes59e+ZXDhXmPE+tForD+lH73Q6EluiozfaldnzWQUWQzdprPk87lg44nkTKN+DT/10S7lW4VYz8wWucOTAPtl5e4mgfjmu0/b3HdZiEG67DJNxwbxlGhwkAuZeXAJS3Qpfemq7dds1tS5dsbc6dAyQpS5uGe+lKrJLSGUqlCb2GcwUuCxBzt71T2/g7t9mQniofv0yjWOtMYdSLM6Sy0pd5iLdFSQtUyiJtRnjmGOdhqq5bo5WzUXAYzns2Lu2tjaqb0WaTHRBrR9cvEVG4VF3WkLsGnzXqohzjbk3dt4hG/jDDxy8BLL5y5miBZi1wa9vT14dJ0o2qft6/1GhQZ1SV9uJxd3cQ7j+XD7RJ40JK38/XAPKz4ly+OG+KwOTDwn0uDSKEZ58/vgH+hmHLcA97uPvCN+G5H/wMoCaQ/KkAAtzdg/DCZ9cmsipsGS4ce5u7z38DYHhmbTL2YfjBH28DOM80s+MoxllVvfkwKudSbiL0dB0NTya2iGpNYmIzl+/EdexjQ8PEGE4FhdPHMAlbLhcsdWaPnfDEAxQJnbx53TEPJ51j3N7CrEfbSNt+arzXt57X2RBx94LsUGHOGRQtF7Fa8HFQQOabJmc5XQ8b8iAbh0mYNFzvdefD+nRhyPowqWitc2VbRyutGCF18+ilU2mEXWX51zFuKbqlZ/RLy0gixzagiS6sgL2hghuwAywarsMBxgzXO9u2sBzZWHwHRLwrQ5rWYQBIfuwCKnZJEpvEYSg9dRoncnejtdxFbBRLqFQzr5fSudH3nDmOaH26yHIwNcZ1NIZNmwWArYU1Fg8HDLB/7wH879VfAey2Rd0a9g/+2ubUyZUOdAz//umXjT136GPd2cDNnM9bC4Pd1gbOx3WsDh/jOkzCDddhEpcjmKiFhvGLQwDitJNrYTz05H7MS+N56hiq0mbYCfeIj2STb2s+cSJEOrguJ4fScaneOW7kOWZJm4VCmaPFg8wKgcSGuLpzR49Rerm8vIRaaECgvyB1Tbl9qOZoMiykHeVhVoZKwW9N+CSJuPwsH4YY12aTa5TxYyZPpsxSDG/Rhgp1lyxUnK/7UMFhEm64DpNIlnzTAdXcsJml8rdO1yt/K+R45EJUluS9zHaWITuQJb9rsVT+HvuKe+RvhdIIcE3ey4Rj+VDBYRJuuA6TcMN1mMT15SWMZ5h10Oc86+dr50s14QWch7rEh5PHef+psgsyqB0iI2e+hE+pDlpvvkQ/uVUMDfdSnTq12TA58injFUdOMPB5AeiALtHcUrstXrqSINnaoVjxyE5ra1ZipHMsTV2kMiQ8NDw7tdmqQ4WtzNEd9uBjXIdJuOE6TMLoy0sct46KHndNS6d2pW5tp+rW+Jw5rVl2qpP5Oqrcnr52w9RMgbfA8db5tAsp8DGuwyTaGW6DB7ppn9CCzxKnvKz9Kz7j/prUi0cwqQLQDBtvrp5uvMc/Wf00oFAT5FjscbcwMloCt1LPWvTUT41sH+M6TMIN12ESw3UPd8gPtrh7JeTyXvZGn0KD0jSlMms5Sfhw92vkUvXT5tPWt3WbSfjMsSFl3ujlJdy+4xkjnFze+PWrNWXWclqaT6t82vq2bjMJnzk2pMzrQwWHSbjhOkzCDdchxpZchpezwySQvHhiyVMLevPRctXwqeWmfcv5GaVTGKRy557YIHnhpETeoCl05grhbPlL89HK1vCp5darvZbgo+XEwYcKDpNww3WYxC6/U5PY5oun66MzPHH8L05PpqHKghn+TpjyictkZQLPh4u6yeknvXeWU+JD6TDHJ/cbn93Bi8nnDKdJm8EG2+zIZwBudlbjUOYOpj1frClPwyf3OZuXuaEx3lgWZixKxIfZ911rvJO65PRFVmZjbYY+VHDYhBuuwyTccB0mcdkB0cr5z70pW/pm7Bo+LesgqUsrPjVye9WXkqld8FiizRCi6LBWjmTRPGGG/JZ5ejvoa1ai1qwvlWarbeZDBYdJuOE6TKKP4W7xJdFb4+R8ZvH5P852gxhpwOZ9AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIwLTA4LTIzVDE0OjUyOjAwKzAyOjAwetRgVgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMC0wOC0yM1QxNDo1MTo1OCswMjowMJuxI+oAAAAASUVORK5CYII=
34+
# yamllint disable-line rule:line-length
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
version: '3.4'
2+
services:
3+
shuffle-tools-fork:
4+
build:
5+
context: .
6+
dockerfile: Dockerfile
7+
# image: walkoff_registry:5000/walkoff_app_HelloWorld-v1-0
8+
deploy:
9+
mode: replicated
10+
replicas: 10
11+
restart_policy:
12+
condition: none
13+
restart: "no"
14+
secrets:
15+
- secret1
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
ioc_finder==7.2.1
2+
py7zr==0.11.3
3+
rarfile==4.0
4+
pyminizip==0.2.4
5+
requests==2.25.1
6+
xmltodict==0.11.0
7+
json2xml==5.0.5
8+
ipaddress==1.0.23
9+
google.auth==1.23.0
10+
paramiko==3.1.0
11+
shufflepy

shuffle-tools-fork/1.0.0/run.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Build testing
2+
NAME=frikky/shuffle:shuffle-tools-fork_1.0.0
3+
docker rmi $NAME --force
4+
docker build . -t frikky/shuffle:shuffle-tools-fork_1.0.0
5+
6+
# Run testing
7+
#docker run -e SHUFFLE_SWARM_CONFIG=run -e SHUFFLE_APP_EXPOSED_PORT=33334 frikky/shuffle:shuffle-tools_1.1.0
8+
echo $NAME
9+
#docker service create --env SHUFFLE_SWARM_CONFIG=run --env SHUFFLE_APP_EXPOSED_PORT=33334 $NAME
10+
11+
#cat walkoff_app_sdk/app_base.py #cat walkoff_app_sdk/app_sdk.py
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import hmac
2+
import datetime
3+
import json
4+
import time
5+
import markupsafe
6+
import os
7+
import re
8+
import subprocess
9+
import tempfile
10+
import zipfile
11+
import base64
12+
import importlib
13+
import ipaddress
14+
import hashlib
15+
import shufflepy
16+
from io import StringIO
17+
from contextlib import redirect_stdout
18+
import random
19+
import string
20+
21+
import xmltodict
22+
from json2xml import json2xml
23+
from json2xml.utils import readfromstring
24+
25+
from ioc_finder import find_iocs
26+
from dateutil.parser import parse as dateutil_parser
27+
from google.auth import crypt
28+
from google.auth import jwt
29+
30+
import py7zr
31+
import pyminizip
32+
import rarfile
33+
import requests
34+
import tarfile
35+
import binascii
36+
import struct
37+
38+
import paramiko
39+
import concurrent.futures
40+
import multiprocessing
41+
42+
from pip._internal import main as pip_main
43+
from pip._internal.commands.show import search_packages_info
44+
45+
from walkoff_app_sdk.app_base import AppBase
46+
47+
class Tools(AppBase):
48+
__version__ = "1.2.0"
49+
app_name = (
50+
"Shuffle Tools Fork" # this needs to match "name" in api.yaml for WALKOFF to work
51+
)
52+
53+
def __init__(self, redis, logger, console_logger=None):
54+
"""
55+
Each app should have this __init__ to set up Redis and logging.
56+
:param redis:
57+
:param logger:
58+
:param console_logger:
59+
"""
60+
super().__init__(redis, logger, console_logger)
61+
62+
def dynamic_import(package_name: str):
63+
"""Import a package and return the module"""
64+
return importlib.import_module(package_name.split('==')[0].split('>=')[0].split('<=')[0].split('>')[0].split('<')[0])
65+
66+
67+
def get_missing_packages(required_packages: list) -> list:
68+
"""
69+
Returns a list of packages that aren't currently installed.
70+
71+
Args:
72+
required_packages: List of package names (can include version specs)
73+
74+
Returns:
75+
List of package names that aren't installed
76+
"""
77+
missing = []
78+
for package in required_packages:
79+
# Remove version specifiers if present (e.g., 'pandas>=1.0.0' -> 'pandas')
80+
package_name = package.split('==')[0].split('>=')[0].split('<=')[0].split('>')[0].split('<')[0].strip()
81+
82+
# Check if package exists in environment
83+
if not list(search_packages_info([package_name])):
84+
missing.append(package)
85+
86+
return missing
87+
88+
def install_packages(self, packages=[]) -> None:
89+
"""
90+
Install Python packages using pip's Python interface.
91+
92+
Args:
93+
packages: List of package names to install
94+
"""
95+
96+
packages_not_found = self.get_missing_packages(packages)
97+
98+
for package in packages_not_found:
99+
try:
100+
pip_main(['install', package])
101+
print(f"Successfully installed {package}")
102+
except Exception as e:
103+
print(f"Failed to install {package}: {str(e)}")
104+
105+
def execute_python(self, code, packages) -> dict:
106+
if os.getenv("SHUFFLE_ALLOW_PACKAGE_INSTALL") == "true":
107+
allow_package_install = True
108+
109+
packages = packages.split("\n") if packages else []
110+
111+
if packages:
112+
if allow_package_install:
113+
self.install_packages(packages)
114+
self.dynamic_import(packages)
115+
116+
if len(code) == 36 and "-" in code:
117+
filedata = self.get_file(code)
118+
if filedata["success"] == False:
119+
return {
120+
"success": False,
121+
"message": f"Failed to get file for ID {code}",
122+
}
123+
124+
if ".py" not in filedata["filename"]:
125+
return {
126+
"success": False,
127+
"message": f"Filename needs to contain .py",
128+
}
129+
130+
131+
# Write the code to a file
132+
# 1. Take the data into a file
133+
# 2. Subprocess execute file?
134+
try:
135+
f = StringIO()
136+
def custom_print(*args, **kwargs):
137+
return print(*args, file=f, **kwargs)
138+
139+
#with redirect_stdout(f): # just in case
140+
# Add globals in it too
141+
globals_copy = globals().copy()
142+
globals_copy["print"] = custom_print
143+
144+
# Add self to globals_copy
145+
for key, value in locals().copy().items():
146+
if key not in globals_copy:
147+
globals_copy[key] = value
148+
149+
globals_copy["self"] = self
150+
151+
exec(code, globals_copy)
152+
153+
s = f.getvalue()
154+
f.close() # why: https://www.youtube.com/watch?v=6SA6S9Ca5-U
155+
156+
#try:
157+
# s = s.encode("utf-8")
158+
#except Exception as e:
159+
160+
try:
161+
return {
162+
"success": True,
163+
"message": json.loads(s.strip()),
164+
}
165+
except Exception as e:
166+
try:
167+
return {
168+
"success": True,
169+
"message": s.strip(),
170+
}
171+
except Exception as e:
172+
return {
173+
"success": True,
174+
"message": s,
175+
}
176+
177+
except Exception as e:
178+
return {
179+
"success": False,
180+
"message": f"exception: {e}",
181+
}
182+
183+
if __name__ == "__main__":
184+
Tools.run()

0 commit comments

Comments
 (0)