diff --git a/.env b/.env index 50427535..7e073bd9 100644 --- a/.env +++ b/.env @@ -25,11 +25,16 @@ SHUFFLE_APP_HOTLOAD_FOLDER=./shuffle-apps SHUFFLE_APP_HOTLOAD_LOCATION=./shuffle-apps SHUFFLE_FILE_LOCATION=./shuffle-files +# Encryption modifier. This HAS to be set to encrypt any authentication being used in Shuffle. This is put together with other relevant values to ensure multiple parts are needed to decrypt. +# If this key is lost or changed, you will have to reauthenticate all apps. +SHUFFLE_ENCRYPTION_MODIFIER= + # Other configs BACKEND_HOSTNAME=shuffle-backend BACKEND_PORT=5001 FRONTEND_PORT=3001 FRONTEND_PORT_HTTPS=3443 + # CHANGE THIS IF YOU WANT GOOD LOCAL EXECUTIONS: OUTER_HOSTNAME=shuffle-backend DB_LOCATION=./shuffle-database @@ -41,6 +46,8 @@ HTTP_PROXY= HTTPS_PROXY= SHUFFLE_PASS_WORKER_PROXY=TRUE SHUFFLE_PASS_APP_PROXY=FALSE +TZ=Europe/Amsterdam # Timezone-handler in Orborus, Worker and Apps +ORBORUS_CONTAINER_NAME= # Used to FIND the containername. cgroup v2: issue 501 SHUFFLE_BASE_IMAGE_REGISTRY=ghcr.io SHUFFLE_BASE_IMAGE_NAME=frikky @@ -60,4 +67,5 @@ SHUFFLE_OPENSEARCH_CERTIFICATE_FILE= SHUFFLE_OPENSEARCH_APIKEY= SHUFFLE_OPENSEARCH_CLOUDID= SHUFFLE_OPENSEARCH_PROXY= +SHUFFLE_OPENSEARCH_INDEX_PREFIX= SHUFFLE_OPENSEARCH_SKIPSSL_VERIFY=true diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index d3f66e5b..d22fd6f0 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -16,7 +16,7 @@ As with everything else, app creation for Shuffle is made as accessibl as possib Workflows are where the magic of Shuffle automation happens. Our current ones [are outlined here](https://github.com/frikky/security-openapis), and will be automatically imported into Shuffle instances in the future. They are split into Prepare and Response, but don't necessarily have to be. If you'd like to talk about workflow creation or use-cases in general, either Open a [new issue](https://github.com/frikky/shuffle-workflows/issues/new) or send us an email at [frikky@shuffler.io](mailto:frikky@shuffler.io) #### Documentation (Markdown) -Documentation is essential to any product, and Shuffle is no exception. Documentation in Shuffle uses markdown and is located in the [shuffle-docs](https://github.com/frikky/shuffle-docs/tree/master/docs) repository. These are then loaded into Shuffle when someone visits [https://shuffler/docs/about](https://shuffler/docs/about), then cached for later use. If you make an edit, expect it on our website in about an hour. +Documentation is essential to any product, and Shuffle is no exception. Documentation in Shuffle uses markdown and is located in the [shuffle-docs](https://github.com/frikky/shuffle-docs/tree/master/docs) repository. These are then loaded into Shuffle when someone visits [https://shuffler/docs/about](https://shuffler/docs/about), then cached for later use. If you make an edit, expect it on our website in about an hour. #### Frontend (ReactJS) The frontend of Shuffle is what everyone sees when they log in. Our goal here is to make it easy to get started and keep going with Shuffle - removing any blockers from the point of accessibility. If you'd like to get started, find [an issue](https://github.com/frikky/Shuffle/issues) and check the [installation guide](https://github.com/frikky/Shuffle/blob/master/install-guide.md#local-development-installation) for setting it up locally without Docker. @@ -24,6 +24,15 @@ The frontend of Shuffle is what everyone sees when they log in. Our goal here is #### Backend (Golang) The backend of Shuffle is our REST API Server that runs in the background, handling all the API-calls in general, whether from users or apps. If you'd like to get started, find [an issue](https://github.com/frikky/Shuffle/issues) and check the [installation guide](https://github.com/frikky/Shuffle/blob/master/install-guide.md#local-development-installation) for setting it up locally without Docker. +#### Scaling (Golang & Python) +Shuffle runs using Docker, and is built to scale. There are many areas that may revolve around scaling, but the main issues come down to how we use Docker in our [architecture](https://shuffler.io/docs/architecture). If you want to help by submitting Helm charts (K8s), Docker swarm configurations, blogposts, or talk about code changes that would help scaling - please reach out (or just start building!), and we can discuss the possibilities. Make sure to read about the architecture first :) + +#### Testing +Whether it's security testing, code testing or CI/CD, we could always need another hand. E.g. an example of CI/CD used for apps can be found [here](https://github.com/Shuffle/Shuffle-apps/blob/master/.github/workflows/ci.yaml), but we don't at all limit the scope to Github actions. If you find a security issue, whether open source or not, please contact [security@shuffler.io](mailto:security@shuffler.io) or [contact us on our website](https://shuffler.io/contact). + +#### Community +What is a product without a community? Want to help out? Whether it be through blogposts, videos or community management, don't hesitate to [reach out](https://shuffler.io/contact) if you would like to help, and get a more keen understanding of how we work. (PS: We're hiring) + ## Working on an issue **Shuffle** uses the [GitHub flow](https://guides.github.com/introduction/flow/index.html). All project changes are made through pull requests. diff --git a/.github/install-guide.md b/.github/install-guide.md index 14440867..d1dfcf76 100644 --- a/.github/install-guide.md +++ b/.github/install-guide.md @@ -18,13 +18,6 @@ cd Shuffle 3. Fix prerequisites for the Opensearch database (Elasticsearch): ``` sudo chown 1000:1000 -R shuffle-database # Required for Opensearch -sudo sysctl -w vm.max_map_count=262144 # https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html - -# To make the changes permanent, do: -# 1. Open the file /etc/sysctl.conf -# 2. Go to the bottom of the file -# 3. Add this line: -vm.max_map_count=262144 ``` 4. Run docker-compose. @@ -38,26 +31,20 @@ When you're done, skip to the "After installation" step below. This step is for setting up with Docker on windows from scratch. 1. Make sure you have [Docker](https://docs.docker.com/docker-for-windows/install/) and [docker-compose](https://docs.docker.com/compose/install/) installed. WSL2 may be required. + 2. Go to https://github.com/frikky/shuffle/releases and download the latest .zip release (or install git) + 3. Unzip the folder and enter it + 4. Open the .env file and change the line with "OUTER_HOSTNAME" to contain your IP: + ``` OUTER_HOSTNAME=YOUR.IP.HERE ``` -5. Configure max memory (WSL) by opening a new CMD/Powershell window. Required for Elasticsearch -``` -wsl -d docker-desktop -sysctl -w vm.max_map_count=262144 -echo "vm.max_map_count = 262144" > /etc/sysctl.d/99-docker-desktop.conf -echo -e "\nvm.max_map_count = 262144\n" >> /etc/sysctl.d/00-alpine.conf - -# https://stackoverflow.com/questions/42111566/elasticsearch-in-windows-docker-image-vm-max-map-count -``` - 6. Run docker-compose ``` -docker-compose up -d +docker compose up -d ``` ### Configurations (proxies, default users etc.) @@ -79,9 +66,11 @@ https://shuffler.io/docs/configuration * Default database location is in the same folder: ./shuffle-database # Local development installation -Local development is pretty straight forward with **ReactJS** and **Golang**. This part is intended to help you run the code for development purposes. + +Local development is pretty straight forward with **ReactJS** and **Golang**. This part is intended to help you run the code for development purposes. We recommend having Shuffle running with the Docker-compose, then manually running the portion that you want to test and/or edit. **PS: You have to stop the Backend Docker container to get this one working** + **PPS: Use the "Launch" branch when developing to get it set up easier** ## Frontend - ReactJS /w cytoscape @@ -96,17 +85,25 @@ npm start http://localhost:5001 - REST API - requires [>=go1.13](https://golang.org/dl/) ```bash export SHUFFLE_OPENSEARCH_URL="http://localhost:9200" +export SHUFFLE_ELASTIC=true cd backend/go-app go run *.go ``` +Large portions of the backend is written in another repository - [shuffle-shared](https://github.com/frikky/shuffle-shared). If you want to update any of this code and test in realtime, we recommend following these steps: +1. Clone shuffle-shared to a local repository +2. Open the Shuffle backend's go.mod file (./shuffle/backend/go.mod) (**NOT** in shuffle-shared) +3. Change the following line to point to your directory AFTER the => +``` +//replace github.com/frikky/shuffle-shared => ../../../../git/shuffle-shared +``` +4. Make the changes you want, then restart the backend server! +5. With your changes made, make a pull request :fire: + **WINDOWS USERS:** You'll have to to add the "export" part as an environment variable. -## Database - Datastore -Based on Google datastore -``` -docker run -p 8000:8000 google/cloud-sdk gcloud beta emulators datastore start --project=shuffle --host-port 0.0.0.0:8000 --no-store-on-disk -``` +## Database - Opensearch +Make sure this is running through the docker-compose, and that the backend points to it with SHUFFLE_OPENSEARCH_URL defined ## Orborus Execution of Workflows: diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..6b235f6b --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,66 @@ +name: ci + +on: + push: + branches: master +jobs: + main: + runs-on: ubuntu-latest + continue-on-error: ${{ matrix.experimental }} + strategy: + fail-fast: false + matrix: + include: + - app: frontend + path: frontend + version: 0.8.3 + experimental: true + - app: backend + path: backend + version: 0.8.3 + experimental: false + - app: orborus + path: functions/onprem/orborus + version: 0.8.0 + experimental: false + - app: database + path: backend/database + version: 0.8.0 + experimental: false + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Set up QEMU + uses: docker/setup-qemu-action@v1 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - + name: Login to DockerHub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} +# Use below configuration for ghcr.io +# with: +# registry: ghcr.io +# username: ${{ github.repository_owner }} +# password: ${{ secrets.CR_PAT }} + - + name: Build and push + id: docker_build + uses: docker/build-push-action@v2 + env: + BUILDX_NO_DEFAULT_LOAD: true + with: + context: ${{ matrix.path }}/ + file: ${{ matrix.path }}/Dockerfile + platforms: linux/amd64,linux/arm64 + #,linux/386 - no node image I guess? + push: true + tags: ${{ secrets.DOCKERHUB_USERNAME }}/${{ matrix.app }}:${{ matrix.version }} + - + name: Image digest + run: echo ${{ steps.docker_build.outputs.digest }} \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile index 5d7c484d..f09fe3b3 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.16.0-buster as builder +FROM golang:1.17.2-buster as builder # Add files RUN mkdir /app @@ -7,16 +7,11 @@ WORKDIR /app ADD ./go-app/main.go /app ADD ./go-app/walkoff.go /app ADD ./go-app/docker.go /app -ADD ./go-app/oauth2.go /app ADD ./go-app/go.mod /app # Required files for code generation ADD ./app_sdk/app_base.py /app_sdk -ADD ./app_sdk_kali/app_base.py /app_sdk_kali -ADD ./app_sdk_kali/static_baseline.py /app_sdk_kali -ADD ./app_sdk_blackarch/app_base.py /app_sdk_blackarch -ADD ./app_sdk_blackarch/static_baseline.py /app_sdk_blackarch ADD ./app_gen /app_gen RUN go get -v @@ -27,12 +22,10 @@ RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o webapp . FROM alpine:latest as certs RUN apk --update add ca-certificates -FROM alpine:3.12 +FROM alpine:3.14.2 COPY --from=builder /app/ /app COPY --from=builder /app_sdk/ /app_sdk -COPY --from=builder /app_sdk_kali/ /app_sdk_kali -COPY --from=builder /app_sdk_blackarch/ /app_sdk_blackarch COPY --from=builder /app_gen/ /app_gen COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt diff --git a/backend/app_gen/openapi/baseline/Dockerfile b/backend/app_gen/openapi/baseline/Dockerfile index 740fee62..8512e833 100644 --- a/backend/app_gen/openapi/baseline/Dockerfile +++ b/backend/app_gen/openapi/baseline/Dockerfile @@ -2,24 +2,27 @@ 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 +#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 +#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 +#RUN mkdir /install +#WORKDIR /install +#COPY requirements.txt /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 -# 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 +#RUN apk --no-cache add --update libmagic +WORKDIR / +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/usr/local" -r /requirements.txt +COPY src /app # Finally, lets run our app! WORKDIR /app diff --git a/backend/app_sdk/Dockerfile b/backend/app_sdk/Dockerfile index 5fcf1901..25c9aa40 100644 --- a/backend/app_sdk/Dockerfile +++ b/backend/app_sdk/Dockerfile @@ -1,16 +1,20 @@ -FROM python:3.9.4-alpine as base +#FROM python:3.9.1-alpine as base +FROM python:3.10.0-alpine as base FROM base as builder -RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev +RUN apk --no-cache add --update alpine-sdk libffi libffi-dev musl-dev openssl-dev tzdata coreutils RUN mkdir /install WORKDIR /install +FROM base + +#--no-cache +RUN apk update && apk add --update tzdata libmagic alpine-sdk libffi libffi-dev musl-dev openssl-dev coreutils + +COPY --from=builder /install /usr/local COPY requirements.txt /requirements.txt RUN pip3 install -r /requirements.txt -FROM base - -COPY --from=builder /install /usr/local COPY __init__.py /app/walkoff_app_sdk/__init__.py COPY app_base.py /app/walkoff_app_sdk/app_base.py diff --git a/backend/app_sdk/Dockerfile_blackarch b/backend/app_sdk/Dockerfile_blackarch new file mode 100644 index 00000000..02480aad --- /dev/null +++ b/backend/app_sdk/Dockerfile_blackarch @@ -0,0 +1,19 @@ +FROM peterclemenko/blackarch as base + +FROM base as builder + +RUN /bin/pacman -Syu --noconfirm + +RUN /bin/pacman -Sy --noconfirm base-devel libffi musl openssl python python-pip -y + +RUN mkdir /install +WORKDIR /install + +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +FROM base + +COPY --from=builder /install /usr/local +COPY __init__.py /app/walkoff_app_sdk/__init__.py +COPY app_base.py /app/walkoff_app_sdk/app_base.py diff --git a/backend/app_sdk/Dockerfile_kali b/backend/app_sdk/Dockerfile_kali new file mode 100644 index 00000000..af7fd24f --- /dev/null +++ b/backend/app_sdk/Dockerfile_kali @@ -0,0 +1,19 @@ +FROM kalilinux/kali-rolling as base + +FROM base as builder + +RUN apt-get update +RUN apt-get dist-upgrade -y +RUN apt install build-essential libffi-dev musl-dev openssl python3 python3-pip -y + +RUN mkdir /install +WORKDIR /install + +COPY requirements.txt /requirements.txt +RUN pip install --prefix="/install" -r /requirements.txt + +FROM base + +COPY --from=builder /install /usr/local +COPY __init__.py /app/walkoff_app_sdk/__init__.py +COPY app_base.py /app/walkoff_app_sdk/app_base.py diff --git a/backend/app_sdk/app_base.py b/backend/app_sdk/app_base.py index a4da37fb..b725ae9f 100644 --- a/backend/app_sdk/app_base.py +++ b/backend/app_sdk/app_base.py @@ -10,6 +10,10 @@ import urllib.parse import http.client import urllib3 import hashlib +from liquid import Liquid +import liquid +import zipfile +from io import BytesIO class AppBase: __version__ = None @@ -26,17 +30,29 @@ class AppBase: self.url = os.getenv("CALLBACK_URL", "https://shuffler.io") self.base_url = os.getenv("BASE_URL", "https://shuffler.io") self.action = os.getenv("ACTION", "") + self.original_action = os.getenv("ACTION", "") self.authorization = os.getenv("AUTHORIZATION", "") self.current_execution_id = os.getenv("EXECUTIONID", "") self.full_execution = os.getenv("FULL_EXECUTION", "") self.start_time = int(time.time()) self.result_wrapper_count = 0 + self.action_result = { + "action": self.action, + "authorization": self.authorization, + "execution_id": self.current_execution_id, + "result": f"", + "started_at": self.start_time, + "status": "", + "completed_at": int(time.time()), + } + if isinstance(self.action, str): try: self.action = json.loads(self.action) + self.original_action = json.loads(self.action) except: - print("[WARNING] Failed parsing action as JSON") + self.logger.info("[WARNING] Failed parsing action as JSON") if len(self.base_url) == 0: self.base_url = self.url @@ -47,45 +63,50 @@ class AppBase: if action_result["status"] == "EXECUTING": action_result["status"] = "FAILURE" + self.logger.info(f"""[DEBUG] Inside Send result with status {action_result["status"]}""") + # FIXME: Add cleanup of parameters to not send to frontend here params = {} #action = action_result["action"] #try: # for item in action["authentication"]: # for action["parameters"] - # print("AUTH: ", key, value) + # self.logger.info("AUTH: ", key, value) # params[item["key"]] = item["value"] #except KeyError: - # print("No authentication specified!") + # self.logger.info("No authentication specified!") # pass # I wonder if this actually works - self.logger.info("Before last stream result") + self.logger.info(f"[DEBUG] Before last stream result") url = "%s%s" % (self.base_url, stream_path) - #print("[INFO] URL (URL): %s" % url) + #self.logger.info("[INFO] URL (URL): %s" % url) try: ret = requests.post(url, headers=headers, json=action_result) - self.logger.info("Result: %d" % ret.status_code) - if ret.status_code != 200: - self.logger.info(ret.text) + #self.logger.info(f"[DEBUG] Result: {ret.status_code}") + #if ret.status_code != 200: + # self.logger.info(f"[DEBUG] Shuffle Response: {ret.text}") + + self.logger.info(f"[DEBUG] Successful request: Status= {ret.status_code} & Response= {ret.text}") except requests.exceptions.ConnectionError as e: - #self.logger.exception("ConnectionError: %s" % e) - self.logger.info("Expected ConnectionError happened") + self.logger.info(f"[DEBUG] Unexpected ConnectionError happened: {e}") return except TypeError as e: #self.logger.exception(e) action_result["status"] = "FAILURE" - action_result["result"] = "POST error: %s" % e - self.logger.info("Before typeerror stream result") + action_result["result"] = f"POST error: {e}" + self.logger.info(f"[DEBUG] Before typeerror stream result: {e}") ret = requests.post("%s%s" % (self.base_url, stream_path), headers=headers, json=action_result) - self.logger.info("Result: %d" % ret.status_code) - if ret.status_code != 200: - self.logger.info(ret.text) + #self.logger.info(f"[DEBUG] Result: {ret.status_code}") + #if ret.status_code != 200: + # pr + + self.logger.info(f"[DEBUG] TypeError request: Status= {ret.status_code} & Response= {ret.text}") except http.client.RemoteDisconnected as e: - self.logger.info("Expected Remotedisconnect happened") + self.logger.info(f"[DEBUG] Expected Remotedisconnect happened: {e}") return except urllib3.exceptions.ProtocolError as e: - self.logger.info("Expected ProtocolError happened") + self.logger.info(f"[DEBUG] Expected ProtocolError happened: {e}") return async def cartesian_product(self, L): @@ -96,19 +117,19 @@ class AppBase: # Handles unique fields by negoiating with the backend def validate_unique_fields(self, params): - #print("IN THE UNIQUE FIELDS PLACE!") + #self.logger.info("IN THE UNIQUE FIELDS PLACE!") newlist = [params] if isinstance(params, list): - #print("ITS A LIST!") + #self.logger.info("ITS A LIST!") newlist = params #self.full_execution = os.getenv("FULL_EXECUTION", "") - #print(len(params)) - #print(params.items()) - #print(list(params.items())) - #print(f"PARAM: {params}") - #print(f"NEWLIST: {newlist}") + #self.logger.info(len(params)) + #self.logger.info(params.items()) + #self.logger.info(list(params.items())) + #self.logger.info(f"PARAM: {params}") + #self.logger.info(f"NEWLIST: {newlist}") # FIXME: Also handle MULTI PARAM values = [] @@ -117,21 +138,21 @@ class AppBase: index = 0 for outerparam in newlist: - #print(f"INNERTYPE: {type(outerparam)}") - #print(f"HANDLING PARAM {key}") + #self.logger.info(f"INNERTYPE: {type(outerparam)}") + #self.logger.info(f"HANDLING PARAM {key}") param_value = "" for key, value in outerparam.items(): - #print("KEY: %s" % key) + #self.logger.info("KEY: %s" % key) #value = params[key] for param in self.action["parameters"]: try: if param["name"] == key and param["unique_toggled"]: - print(f"FOUND: {key} with param {param}!") + self.logger.info(f"[DEBUG] FOUND: {key} with param {param}!") if isinstance(value, dict) or isinstance(value, list): try: value = json.dumps(value) except json.decoder.JSONDecodeError as e: - print(f"Error in json decode for param {value}: {e}") + self.logger.info(f"[WARNING] Error in json decode for param {value}: {e}") continue elif isinstance(value, int) or isinstance(value, float): value = str(value) @@ -140,15 +161,15 @@ class AppBase: elif value == True: value = "True" - print(f"VALUE APPEND: {value}") + self.logger.info(f"[DEBUG] VALUE APPEND: {value}") param_value += value if param["name"] not in param_names: param_names.append(param["name"]) except (KeyError, NameError) as e: - print(f"""Key/NameError in param handler for {param["name"]}: {e}""") + self.logger.info(f"""Key/NameError in param handler for {param["name"]}: {e}""") - print(f"OUTER VALUE: {param_value}") + self.logger.info(f"[DEBUG] OUTER VALUE: {param_value}") if len(param_value) > 0: md5 = hashlib.md5(param_value.encode('utf-8')).hexdigest() values.append(md5) @@ -185,12 +206,12 @@ class AppBase: }] } - #print(f"DATA: {data}") + #self.logger.info(f"DATA: {data}") # 1594869a676630b397bc34f7dc0951a3 - #print(f"VALUE URL: {url}") - #print(f"RET: {ret.text}") - #print(f"ID: {ret.status_code}") + #self.logger.info(f"VALUE URL: {url}") + #self.logger.info(f"RET: {ret.text}") + #self.logger.info(f"ID: {ret.status_code}") url = f"{self.url}/api/v1/orgs/{org_id}/validate_app_values" ret = requests.post(url, json=data) if ret.status_code == 200: @@ -198,27 +219,27 @@ class AppBase: if len(json_value["found"]) > 0: modifier = 0 for item in json_value["found"]: - print(f"Should remove {item}") + self.logger.info(f"Should remove {item}") try: - print(f"FOUND: {all_values[item]}") - print(f"SHOULD REMOVE INDEX: {all_values[item]['index']}") + self.logger.info(f"FOUND: {all_values[item]}") + self.logger.info(f"SHOULD REMOVE INDEX: {all_values[item]['index']}") try: newlist.pop(all_values[item]["index"]-modifier) modifier += 1 except IndexError as e: - print(f"Error popping value from array: {e}") + self.logger.info(f"Error popping value from array: {e}") except (NameError, KeyError) as e: - print(f"Failed removal: {e}") + self.logger.info(f"Failed removal: {e}") #return False else: - print("None of the items were found!") + self.logger.info("None of the items were found!") return newlist else: - print(f"[WARNING] Failed checking values with status code {ret.status_code}!") + self.logger.info(f"[WARNING] Failed checking values with status code {ret.status_code}!") #return True return newlist @@ -279,19 +300,19 @@ class AppBase: #self.action = action loopnames = [] - print(f"Baseparams to check!!: {baseparams}") + #self.logger.info(f"Baseparams to check!!: {baseparams}") for key, value in baseparams.items(): check_value = "" - for param in self.action["parameters"]: + for param in self.original_action["parameters"]: if param["name"] == key: - #print("PARAM: %s" % param) + #self.logger.info("PARAM: %s" % param) check_value = param["value"] # self.result_wrapper_count = 0 octothorpe_count = param["value"].count(".#") if octothorpe_count > self.result_wrapper_count: self.result_wrapper_count = octothorpe_count - print("[INFO] NEW OCTOTHORPE WRAPPER: %d" % octothorpe_count) + self.logger.info("[INFO] NEW OCTOTHORPE WRAPPER: %d" % octothorpe_count) # This whole thing is hard. @@ -312,13 +333,13 @@ class AppBase: # FIXME: Check the above, and fix so that nested looped items can be # Skipped if wanted - #print("\nCHECK: %s" % check_value) + #self.logger.info("\nCHECK: %s" % check_value) #try: # values = parameter["value_replace"] # if values != None: - # print(values) + # self.logger.info(values) # for val in values: - # print(val) + # self.logger.info(val) #except: # pass @@ -327,15 +348,15 @@ class AppBase: should_merge = True # Specific for OpenAPI body replacement - print("\n\n\nDOING STUFF BELOW HERE") + #self.logger.info("\n\n\nDOING STUFF BELOW HERE") if not should_merge: - for parameter in self.action["parameters"]: + for parameter in self.original_action["parameters"]: if parameter["name"] == key: - print("CHECKING BODY FOR VALUE REPLACE DATA!") + #self.logger.info("CHECKING BODY FOR VALUE REPLACE DATA!") try: values = parameter["value_replace"] if values != None: - print(values) + self.logger.info(values) for val in values: if "#" in val["value"]: should_merge = True @@ -343,9 +364,9 @@ class AppBase: except: pass - print(f"MERGE: {should_merge}") + #self.logger.info(f"MERGE: {should_merge}") if isinstance(value, list): - print("Item {value} is a list.") + self.logger.info(f"[DEBUG] Item {value} is a list.") if len(value) <= 1: if len(value) == 1: baseparams[key] = value[0] @@ -354,7 +375,7 @@ class AppBase: # should_merge = True else: if not should_merge: - print("Adding WITHOUT looping list") + self.logger.info("[DEBUG] Adding WITHOUT looping list") else: if len(value) not in listlengths: listlengths.append(len(value)) @@ -368,40 +389,41 @@ class AppBase: all_list_keys.append(key) all_lists.append(baseparams[key]) else: - print(f"{value} is not a list") + #self.logger.info(f"{value} is not a list") + pass - print("Listlengths: %s" % listlengths) + self.logger.info("[DEBUG] Listlengths: %s" % listlengths) if len(listlengths) == 0: - print("NO multiplier. Running a single iteration.") + self.logger.info("[DEBUG] NO multiplier. Running a single iteration.") paramlist.append(baseparams) elif len(listlengths) == 1: - print("NO MULTIPLIER NECESSARY. Length is %d" % len(listitems)) + self.logger.info("[DEBUG] NO MULTIPLIER NECESSARY. Length is %d" % len(listitems)) for item in listitems: # This loops should always be length 1 for key, value in item.items(): if isinstance(value, int): - print("\nShould run key %s %d times from %s" % (key, value, baseparams[key])) + self.logger.info("\n[DEBUG] Should run key %s %d times from %s" % (key, value, baseparams[key])) if len(paramlist) == value: - print("List ALREADY exists - just changing values") + self.logger.info("[DEBUG] List ALREADY exists - just changing values") for subloop in range(value): baseitem = copy.deepcopy(baseparams) paramlist[subloop][key] = baseparams[key][subloop] else: - print("List DOESNT exist - ADDING values") + self.logger.info("[DEBUG] List DOESNT exist - ADDING values") for subloop in range(value): baseitem = copy.deepcopy(baseparams) baseitem[key] = baseparams[key][subloop] paramlist.append(baseitem) else: - print("Multipliers to handle: %s" % listitems) + self.logger.info("[DEBUG] Multipliers to handle: %s" % listitems) newlength = 1 for item in listitems: for key, value in item.items(): newlength = newlength * value - print("Newlength of array: %d. Lists: %s" % (newlength, all_lists)) + self.logger.info("[DEBUG] Newlength of array: %d. Lists: %s" % (newlength, all_lists)) # Get the cartesian product of the arrays cartesian = await self.cartesian_product(all_lists) newlist = [] @@ -416,7 +438,7 @@ class AppBase: paramlist.append(baseitem) - #print("PARAMLIST: %s" % paramlist) + #self.logger.info("PARAMLIST: %s" % paramlist) #newlist[subitem[0]] #if len(newlist) > 0: @@ -424,22 +446,22 @@ class AppBase: # How do we get it back, ordered? #for item in cartesian: - #print("Listlengths: %s" % listlengths) + #self.logger.info("Listlengths: %s" % listlengths) #paramlist = [baseparams] - #print("[INFO] Return paramlist: %s" % paramlist) + #self.logger.info("[INFO] Return paramlist: %s" % paramlist) return paramlist # Runs recursed versions with inner loops and such async def run_recursed_items(self, func, baseparams, loop_wrapper): - print(f"RECURSED ITEMS: {baseparams}") + #self.logger.info(f"RECURSED ITEMS: {baseparams}") has_loop = False newparams = {} for key, value in baseparams.items(): if isinstance(value, list) and len(value) > 0: - print(f"In list check for {key}") + self.logger.info(f"[DEBUG] In list check for {key}") try: # Added skip for body (OpenAPI) which uses data= in requests @@ -447,33 +469,36 @@ class AppBase: if key != "body": value[0] = json.loads(value[0]) except json.decoder.JSONDecodeError as e: - print("JSON casting error: %s" % e) + self.logger.info("[WARNING] JSON casting error (1): %s" % e) except TypeError as e: - print("TypeError: %s" % e) + self.logger.info("[WARNING] TypeError: %s" % e) - print("POST initial list check") + self.logger.info("[DEBUG] POST initial list check") - if isinstance(value, list) and len(value) == 1 and isinstance(value[0], list): - try: - loop_wrapper[key] += 1 - except IndexError: - loop_wrapper[key] = 1 - except KeyError: - loop_wrapper[key] = 1 + try: + if isinstance(value, list) and len(value) == 1 and isinstance(value[0], list): + try: + loop_wrapper[key] += 1 + except Exception as e: + self.logger.info("[WARNING] Exception in loop wrapper: {e}") + loop_wrapper[key] = 1 - print(f"Key {key} is a list: {value}") - newparams[key] = value[0] - has_loop = True - else: - print(f"Key {key} is NOT a list within a list. Value: {value}") + self.logger.info(f"[DEBUG] Key {key} is a list: {value}") + newparams[key] = value[0] + has_loop = True + else: + #self.logger.info(f"Key {key} is NOT a list within a list. Value: {value}") + newparams[key] = value + except Exception as e: + self.logger.info(f"[WARNING] Error in baseparams list: {e}") newparams[key] = value results = [] if has_loop: - print("[WARNING] Should run inner loop: %s" % newparams) + self.logger.info(f"[DEBUG] Should run inner loop: {newparams}") ret = await self.run_recursed_items(func, newparams, loop_wrapper) else: - print("[INFO] Should run multiplier check with params (inner): %s" % newparams) + self.logger.info(f"[DEBUG] Should run multiplier check with params (inner): {newparams}") # 1. Find the loops that are required and create new multipliers # If here: check for multipliers within this scope. ret = [] @@ -481,10 +506,10 @@ class AppBase: # FIXME: This does a deduplication of the data new_params = self.validate_unique_fields(param_multiplier) - print(f"NEW PARAMS: {new_params}") + #self.logger.info(f"NEW PARAMS: {new_params}") if len(new_params) == 0: - print("[WARNING] SHOULD STOP MULTI-EXECUTION BECAUSE FIELDS AREN'T UNIQUE") - action_result = { + self.logger.info("[WARNING] SHOULD STOP MULTI-EXECUTION BECAUSE FIELDS AREN'T UNIQUE") + self.action_result = { "action": self.action, "authorization": self.authorization, "execution_id": self.current_execution_id, @@ -494,29 +519,29 @@ class AppBase: "completed_at": int(time.time()), } - self.send_result(action_result, {"Content-Type": "application/json", "Authorization": "Bearer %s" % self.authorization}, "/api/v1/streams") + self.send_result(self.action_result, {"Content-Type": "application/json", "Authorization": "Bearer %s" % self.authorization}, "/api/v1/streams") exit() #return else: #subparams = new_params - print(f"NEW PARAMS: {new_params}") + #self.logger.info(f"NEW PARAMS: {new_params}") param_multiplier = new_params - #print("Returned with newparams of length %d", len(new_params)) + #self.logger.info("Returned with newparams of length %d", len(new_params)) #if isinstance(new_params, list) and len(new_params) == 1: # params = new_params[0] #else: - # print("[WARNING] SHOULD STOP EXECUTION BECAUSE FIELDS AREN'T UNIQUE") + # self.logger.info("[WARNING] SHOULD STOP EXECUTION BECAUSE FIELDS AREN'T UNIQUE") # action_result["status"] = "SKIPPED" # action_result["result"] = f"A non-unique value was found" # action_result["completed_at"] = int(time.time()) # self.send_result(action_result, headers, stream_path) # return - print("[INFO] Multiplier length: %d" % len(param_multiplier)) + self.logger.info("[INFO] Multiplier length: %d" % len(param_multiplier)) #tmp = "" for subparams in param_multiplier: - print(f"SUBPARAMS IN MULTI: {subparams}") + #self.logger.info(f"SUBPARAMS IN MULTI: {subparams}") try: #tmp = await func(**subparams) @@ -525,6 +550,7 @@ class AppBase: tmp = await func(**subparams) break except TypeError as e: + self.logger.info("BASE TYPEERROR: %s" % e) errorstring = "%s" % e if "got an unexpected keyword argument" in errorstring: fieldsplit = errorstring.split("'") @@ -533,7 +559,7 @@ class AppBase: try: del subparams[field] - print("Removed field invalid field %s" % field) + self.logger.info("Removed field invalid field %s" % field) except KeyError: break else: @@ -544,12 +570,12 @@ class AppBase: try: e = sys.exc_info()[1] except: - print("Exc check fail: %s" % e) + self.logger.info("Exc check fail: %s" % e) pass tmp = "An error occured during execution: %s" % e - print("RET from execution: %s" % ret) + #self.logger.info("RET from execution: %s" % ret) new_value = tmp if tmp == None: new_value = "" @@ -568,7 +594,7 @@ class AppBase: pass except: pass - #print("Json: %s" % e) + #self.logger.info("Json: %s" % e) #ret.append(tmp) #if self.result_wrapper_count > 0: @@ -576,12 +602,12 @@ class AppBase: #else: ret.append(new_value) - print("Ret length: %d" % len(ret)) + self.logger.info("[INFO] Ret length: %d" % len(ret)) if len(ret) == 1: #ret = ret[0] - print("DONT make list of 1 into 0!!") + self.logger.info("[DEBUG] DONT make list of 1 into 0!!") - print("Return from execution: %s" % ret) + self.logger.info("Return from execution: %s" % ret) if ret == None: results.append("") json_object = False @@ -598,7 +624,7 @@ class AppBase: results.append(json.loads(ret)) json_object = True except json.decoder.JSONDecodeError as e: - #print("Json: %s" % e) + #self.logger.info("Json: %s" % e) results.append(ret) except TypeError as e: results.append(ret) @@ -607,12 +633,28 @@ class AppBase: if len(results) == 1: #results = results[0] - print("DONT MAKE LIST FROM 1 TO 0!!") + self.logger.info("DONT MAKE LIST FROM 1 TO 0!!") - print("\nLOOP: %s\nRESULTS: %s" % (loop_wrapper, results)) + self.logger.info("\nLOOP: %s\nRESULTS: %s" % (loop_wrapper, results)) return results + # Downloads all files from a namespace + # Currently only working on local version of Shuffle + def get_file_namespace(self, namespace): + org_id = self.full_execution["workflow"]["execution_org"]["id"] + get_path = "/api/v1/files/namespaces/%s?execution_id=%s" % (namespace, self.full_execution["execution_id"]) + headers = { + "Authorization": "Bearer %s" % self.authorization + } + + ret1 = requests.get("%s%s" % (self.url, get_path), headers=headers) + if ret1.status_code != 200: + return None + + filebytes = BytesIO(ret1.content) + myzipfile = zipfile.ZipFile(filebytes) + return myzipfile # Things to consider for files: # - How can you download / stream a file? @@ -621,10 +663,10 @@ class AppBase: full_execution = self.full_execution org_id = full_execution["workflow"]["execution_org"]["id"] - print("SHOULD GET FILES BASED ON ORG %s, workflow %s and value(s) %s" % (org_id, full_execution["workflow"]["id"], value)) + self.logger.info("SHOULD GET FILES BASED ON ORG %s, workflow %s and value(s) %s" % (org_id, full_execution["workflow"]["id"], value)) if isinstance(value, list): - print("IS LIST!") + self.logger.info("IS LIST!") #if len(value) == 1: # value = value[0] else: @@ -632,9 +674,9 @@ class AppBase: returns = [] for item in value: - print("VALUE: %s" % item) + self.logger.info("VALUE: %s" % item) if len(item) != 36: - print("Bad length for file value %s" % item) + self.logger.info("Bad length for file value %s" % item) continue #return { # "filename": "", @@ -649,7 +691,7 @@ class AppBase: } ret1 = requests.get("%s%s" % (self.url, get_path), headers=headers) - print("RET1 (file get): %s" % ret1.text) + self.logger.info("RET1 (file get): %s" % ret1.text) if ret1.status_code != 200: returns.append({ "filename": "", @@ -660,7 +702,7 @@ class AppBase: content_path = "/api/v1/files/%s/content?execution_id=%s" % (item, full_execution["execution_id"]) ret2 = requests.get("%s%s" % (self.url, content_path), headers=headers) - print("RET2 (file get) done") + self.logger.info("RET2 (file get) done") if ret2.status_code == 200: tmpdata = ret1.json() returndata = { @@ -670,7 +712,7 @@ class AppBase: } returns.append(returndata) - print("RET3 (file get done)") + self.logger.info("RET3 (file get done)") if len(returns) == 0: return { @@ -702,7 +744,7 @@ class AppBase: allvalues["value"] = str(value) return allvalues except: - print("Value couldn't be parsed") + self.logger.info("Value couldn't be parsed") #return response.json() return {"success": False} @@ -720,22 +762,25 @@ class AppBase: value = requests.post(url, json=data) try: allvalues = value.json() - print("VAL1: ", allvalues) + self.logger.info("VAL1: ", allvalues) allvalues["key"] = key - print("VAL2: ", allvalues) + self.logger.info("VAL2: ", allvalues) try: parsedvalue = json.loads(allvalues["value"]) allvalues["value"] = parsedvalue except: - print("Parsing of value as JSON failed. Continue anyway!") + self.logger.info("Parsing of value as JSON failed. Continue anyway!") return allvalues except: - print("Value couldn't be parsed, or json dump of value failed") + self.logger.info("Value couldn't be parsed, or json dump of value failed") #return value.json() return {"success": False} + def set_file(self, infiles): + return self.set_files(infiles) + # Sets files in the backend def set_files(self, infiles): full_execution = self.full_execution @@ -763,28 +808,28 @@ class AppBase: data["filename"] = curfile["filename"] filename = curfile["filename"] except KeyError as e: - print(f"KeyError in file setup: {e}") + self.logger.info(f"KeyError in file setup: {e}") pass ret = requests.post("%s%s" % (self.url, create_path), headers=headers, json=data) - print(f"Ret CREATE: {ret.text}") + self.logger.info(f"Ret CREATE: {ret.text}") cur_id = "" if ret.status_code == 200: - print("RET: %s" % ret.text) + self.logger.info("RET: %s" % ret.text) ret_json = ret.json() if not ret_json["success"]: - print("Not success in file upload creation.") + self.logger.info("Not success in file upload creation.") continue - print("Should handle ID %s" % ret_json["id"]) + self.logger.info("Should handle ID %s" % ret_json["id"]) file_ids.append(ret_json["id"]) cur_id = ret_json["id"] else: - print("Bad status code: %d" % ret.status_code) + self.logger.info("Bad status code: %d" % ret.status_code) continue if len(cur_id) == 0: - print("No file ID specified from backend") + self.logger.info("No file ID specified from backend") continue new_headers = { @@ -792,16 +837,16 @@ class AppBase: } upload_path = "/api/v1/files/%s/upload?execution_id=%s" % (cur_id, full_execution["execution_id"]) - print("Create path: %s" % create_path) + self.logger.info("Create path: %s" % create_path) files={"shuffle_file": (filename, curfile["data"])} #open(filename,'rb')} ret = requests.post("%s%s" % (self.url, upload_path), files=files, headers=new_headers) - print("Ret UPLOAD: %s" % ret.text) - print("Ret2 UPLOAD: %d" % ret.status_code) + self.logger.info("Ret UPLOAD: %s" % ret.text) + self.logger.info("Ret2 UPLOAD: %d" % ret.status_code) - print("IDS TO RETURN: %s" % file_ids) + self.logger.info("IDS TO RETURN: %s" % file_ids) return file_ids async def execute_action(self, action): @@ -809,7 +854,7 @@ class AppBase: # !!! Let this line stay - its used for some horrible codegeneration / stitching !!! # #STARTCOPY stream_path = "/api/v1/streams" - action_result = { + self.action_result = { "action": action, "authorization": self.authorization, "execution_id": self.current_execution_id, @@ -827,7 +872,7 @@ class AppBase: pass self.action = copy.deepcopy(action) - self.logger.info("Sending starting action result (EXECUTING)") + self.logger.info("[DEBUG] Sending starting action result (EXECUTING)") headers = { "Content-Type": "application/json", @@ -835,21 +880,21 @@ class AppBase: } if len(self.action) == 0: - print("ACTION env not defined") - action_result["result"] = "Error in setup ENV: ACTION not defined" - self.send_result(action_result, headers, stream_path) + self.logger.info("ACTION env not defined") + self.action_result["result"] = "Error in setup ENV: ACTION not defined" + self.send_result(self.action_result, headers, stream_path) return if len(self.authorization) == 0: - print("AUTHORIZATION env not defined") - action_result["result"] = "Error in setup ENV: AUTHORIZATION not defined" - self.send_result(action_result, headers, stream_path) + self.logger.info("AUTHORIZATION env not defined") + self.action_result["result"] = "Error in setup ENV: AUTHORIZATION not defined" + self.send_result(self.action_result, headers, stream_path) return if len(self.current_execution_id) == 0: - print("EXECUTIONID env not defined") - action_result["result"] = "Error in setup ENV: EXECUTIONID not defined" - self.send_result(action_result, headers, stream_path) + self.logger.info("EXECUTIONID env not defined") + self.action_result["result"] = "Error in setup ENV: EXECUTIONID not defined" + self.send_result(self.action_result, headers, stream_path) return @@ -864,7 +909,7 @@ class AppBase: # if ret.status_code != 200: # self.logger.info(ret.text) #except requests.exceptions.ConnectionError as e: - # print("Connectionerror: %s" % e) + # self.logger.info("Connectionerror: %s" % e) # action_result["result"] = "Bad setup during startup: %s" % e # self.send_result(action_result, headers, stream_path) @@ -874,14 +919,14 @@ class AppBase: # If found, we get the full results list from backend fullexecution = {} if len(self.full_execution) == 0: - print("NO EXECUTION - LOADING!") + self.logger.info("[DEBUG] NO EXECUTION - LOADING!") try: tmpdata = { "authorization": self.authorization, "execution_id": self.current_execution_id } - self.logger.info("Before FULLEXEC stream result") + self.logger.info("[DEBUG] Before FULLEXEC stream result") ret = requests.post( "%s/api/v1/streams/results" % (self.base_url), headers=headers, @@ -897,28 +942,28 @@ class AppBase: except json.decoder.JSONDecodeError: pass - action_result["result"] = "Bad result from backend: %d" % ret.status_code - self.send_result(action_result, headers, stream_path) + self.action_result["result"] = "Bad result from backend: %d" % ret.status_code + self.send_result(self.action_result, headers, stream_path) return except requests.exceptions.ConnectionError as e: self.logger.info("Connectionerror: %s" % e) - action_result["result"] = "Connection error during startup: %s" % e - self.send_result(action_result, headers, stream_path) + self.action_result["result"] = "Connection error during startup: %s" % e + self.send_result(self.action_result, headers, stream_path) return else: try: fullexecution = json.loads(self.full_execution) except json.decoder.JSONDecodeError as e: - print("Json decode execution error: %s" % e) - action_result["result"] = "Json error during startup: %s" % e - self.send_result(action_result, headers, stream_path) + self.logger.info("[WARNING] Json decode execution error: %s" % e) + self.action_result["result"] = "Json error during startup: %s" % e + self.send_result(self.action_result, headers, stream_path) return - print("") + self.logger.info("") self.full_execution = fullexecution - self.logger.info("AFTER FULLEXEC stream result (init)") + self.logger.info("[DEBUG] AFTER FULLEXEC stream result (init)") # Gets the value at the parenthesis level you want def parse_nested_param(string, level): @@ -976,8 +1021,8 @@ class AppBase: try: return int(data) except ValueError: - print("ValueError while casting %s to int" % data) return data + if "lower" in thistype: return data.lower() if "upper" in thistype: @@ -988,34 +1033,53 @@ class AppBase: return data.strip() if "split" in thistype: return data.split() + if "replace" in thistype: + splitvalues = data.split(",") + + if len(splitvalues) > 2: + for i in range(len(splitvalues)): + if i != 0: + if splitvalues[i] == " ": + splitvalues[i] = " " + continue + + splitvalues[i] = splitvalues[i].strip() + + if splitvalues[i] == "\"\"": + splitvalues[i] = "" + if splitvalues[i] == "\" \"": + splitvalues[i] = " " + if len(splitvalues[i]) > 2: + if splitvalues[i][0] == "\"" and splitvalues[i][len(splitvalues[i])-1] == "\"": + splitvalues[i] = splitvalues[i][1:-1] + if splitvalues[i][0] == "'" and splitvalues[i][len(splitvalues[i])-1] == "'": + splitvalues[i] = splitvalues[i][1:-1] + + + replacementvalue = splitvalues[0] + return replacementvalue.replace(splitvalues[1], splitvalues[2], -1) + else: + return f"replace({data})" if "join" in thistype: - print(f"SHOULD JOIN: {data}") try: splitvalues = data.split(",") if "," not in data: return f"join({data})" if len(splitvalues) >= 2: - print(f"SPLITVALUE: {splitvalues[-1]}") # 1. Take the list and parse it from string # 2. Take all the items and join them # 3. Parse them back as string and return values = ",".join(splitvalues[0:-1]) - print(f"VALUES: {values}") tmp = json.loads(values) - print(f"TMP: {tmp}") - #tmp = tmp[1:-1] - #print(f"TMP2: {tmp}") try: newvalues = splitvalues[-1].join(str(item).strip() for item in tmp) except TypeError: newvalues = splitvalues[-1].join(json.dumps(item).strip() for item in tmp) - print(f"new: {newvalues}") return newvalues else: - print("Returning default") return f"join({data})" except (KeyError, IndexError) as e: @@ -1024,12 +1088,12 @@ class AppBase: print(f"JSON ERROR in join(): {e}") if "len" in thistype or "length" in thistype or "lenght" in thistype: - print(f"Trying to length-parse: {data}") + #self.logger.info(f"Trying to length-parse: {data}") try: tmp_len = json.loads(data, parse_float=str, parse_int=str, parse_constant=str) except (NameError, KeyError, TypeError, json.decoder.JSONDecodeError) as e: try: - print(f"[WARNING] INITIAL Parsing bug for length in app sdk: {e}") + #self.logger.info(f"[WARNING] INITIAL Parsing bug for length in app sdk: {e}") # data = data.replace("\'", "\"") data = data.replace("True", "true") data = data.replace("False", "false") @@ -1070,12 +1134,12 @@ class AppBase: else: tmp = json.loads(parsedlist)[lastsplit[0]] - #print(tmp) return tmp except IndexError as e: return default_error # Parses the INNER value and recurses until everything is done + # Looks for a way to use e.g. int() or number() as a value def parse_wrapper(data): try: if "(" not in data or ")" not in data: @@ -1083,8 +1147,7 @@ class AppBase: except TypeError: return data, False - wrappers = ["int", "number", "lower", "upper", "trim", "strip", "split", "parse", "len", "length", "lenght", - "join"] + wrappers = ["int", "number", "lower", "upper", "trim", "strip", "split", "parse", "len", "length", "lenght", "join", "replace"] if not any(wrapper in data for wrapper in wrappers): return data, False @@ -1093,9 +1156,6 @@ class AppBase: inner_value = parse_nested_param(data, maxDepth(data) - 0) outer_value = parse_nested_param(data, maxDepth(data) - 1) - print("INNER: ", inner_value) - print("OUTER: ", outer_value) - wrapper_group = "|".join(wrappers) parse_string = data max_depth = maxDepth(parse_string) @@ -1125,7 +1185,7 @@ class AppBase: c_parentheses = parse_nested_param(parse_string, 0)[0] match_string = re.escape(c_parentheses) custom_casting = re.findall(fr"({wrapper_group})\({match_string}", parse_string) - + print("[DEBUG] In ELSE: %s" % custom_casting) # check if a wrapper was found if len(custom_casting) != 0: inner_result = parse_type(c_parentheses, custom_casting[0]) @@ -1137,6 +1197,7 @@ class AppBase: else: parse_string = inner_result + print("PARSE STRING: %s" % parse_string) return parse_string, True # Looks for parantheses to grab special cases within a string, e.g: @@ -1147,20 +1208,37 @@ class AppBase: # = # Authorization=Bearer authkey # ^ Double space. - def parse_wrapper_start(data): + def parse_wrapper_start(data, self): + try: + data = parse_liquid(data, self) + except: + pass + if "(" not in data or ")" not in data: return data + if isinstance(data, str) and len(data) > 4: + if (data[0] == "{" or data[0] == "[") and (data[len(data)-1] == "]" or data[len(data)-1] == "}"): + self.logger.info("Skipping parser because use of {[ and ]}") + return data + newdata = [] newstring = "" record = True paranCnt = 0 + charcnt = 0 for char in data: if char == "(": - paranCnt += 1 - - if not record: - record = True + charskip = False + if charcnt > 0: + if data[charcnt-1] == " ": + charskip = True + + if not charskip: + paranCnt += 1 + + if not record: + record = True if record: newstring += char @@ -1175,6 +1253,8 @@ class AppBase: if paranCnt == 0: record = False + + charcnt += 1 if len(newstring) > 0: newdata.append(newstring) @@ -1195,12 +1275,12 @@ class AppBase: return data if len(parsedlist) > 0 and not non_string: - #print("Returning parsed list: ", parsedlist) + #self.logger.info("Returning parsed list: ", parsedlist) return " ".join(parsedlist) elif len(parsedlist) == 1 and non_string: return parsedlist[0] else: - #print("Casting back to string because multi: ", parsedlist) + #self.logger.info("Casting back to string because multi: ", parsedlist) newlist = [] for item in parsedlist: try: @@ -1212,9 +1292,10 @@ class AppBase: return " ".join(newlist) # Parses JSON loops and such down to the item you're looking for + # $nodename.#.id + # $nodename.data.#min-max.info.id def recurse_json(basejson, parsersplit): match = "#([0-9a-z]+):?-?([0-9a-z]+)?#?" - #print("Split: %s\n%s" % (parsersplit, basejson)) try: outercnt = 0 @@ -1223,9 +1304,7 @@ class AppBase: #if " " in value: # value = value.replace(" ", "_", -1) - #print("VALUE: %s\n" % value) actualitem = re.findall(match, value, re.MULTILINE) - #print("ACTUAL RECURSE: (%s) %s" % (value, actualitem)) if value == "#": newvalue = [] for innervalue in basejson: @@ -1245,21 +1324,24 @@ class AppBase: return newvalue, True elif len(actualitem) > 0: - #print("[INFO] In recursion v2: ", actualitem) is_loop = True newvalue = [] firstitem = actualitem[0][0] seconditem = actualitem[0][1] + print("[DEBUG] ACTUAL PARSED: %s" % actualitem) # Means it's a single item -> continue if seconditem == "": print("[INFO] In first - handling %s. Len: %d" % (firstitem, len(basejson))) if firstitem.lower() == "max" or firstitem.lower() == "last": firstitem = len(basejson)-1 - if firstitem.lower() == "min" or firstitem.lower() == "first": + elif firstitem.lower() == "min" or firstitem.lower() == "first": firstitem = 0 + else: + firstitem = int(firstitem) + print(f"[DEBUG] Post lower checks with item {firstitem}") tmpitem = basejson[int(firstitem)] try: newvalue, is_loop = recurse_json(tmpitem, parsersplit[outercnt+1:]) @@ -1269,13 +1351,19 @@ class AppBase: print("[INFO] In ELSE - handling %s and %s" % (firstitem, seconditem)) if firstitem.lower() == "max" or firstitem.lower() == "last": firstitem = len(basejson)-1 - if firstitem.lower() == "min" or firstitem.lower() == "first": + elif firstitem.lower() == "min" or firstitem.lower() == "first": firstitem = 0 + else: + firstitem = int(firstitem) + if seconditem.lower() == "max" or seconditem.lower() == "last": seconditem = len(basejson)-1 - if seconditem.lower() == "min" or seconditem.lower() == "first": + elif seconditem.lower() == "min" or seconditem.lower() == "first": seconditem = 0 + else: + seconditem = int(seconditem) + print(f"[DEBUG] Post lower checks 2: {firstitem} AND {seconditem}") newvalue = [] if int(seconditem) > len(basejson): seconditem = len(basejson) @@ -1283,24 +1371,20 @@ class AppBase: for i in range(int(firstitem), int(seconditem)+1): # 1. Check the next item (message) # 2. Call this function again - #print("Base: %s" % basejson[i]) + #self.logger.info("Base: %s" % basejson[i]) try: - ret, is_loop = recurse_json(basejson[i], parsersplit[outercnt+1:]) + ret, tmp_loop = recurse_json(basejson[i], parsersplit[outercnt+1:]) except IndexError: - print("INDEXERROR: ", parsersplit[outercnt]) + print("[DEBUG] INDEXERROR: ", parsersplit[outercnt]) #ret = innervalue - ret, is_loop = recurse_json(innervalue, parsersplit[outercnt:]) + ret, tmp_loop = recurse_json(basejson[i], parsersplit[outercnt:]) - #print("IN LIST: %s" % ret) - #exit() newvalue.append(ret) - #print("Returning %s" % newvalue) return newvalue, is_loop else: - #print("BEFORE NORMAL VALUE: ", basejson, value) if len(value) == 0: return basejson, False @@ -1309,12 +1393,9 @@ class AppBase: print("[WARNING] VALUE IN ISINSTANCE IS NOT TO BE USED (list): %s" % value) return basejson, False elif isinstance(basejson[value], str): - print(f"[INFO] LOADING STRING '%s' AS JSON" % basejson[value]) try: basejson = json.loads(basejson[value]) - print("BASEJSON: %s" % basejson) except json.decoder.JSONDecodeError as e: - print("RETURNING BECAUSE '%s' IS A NORMAL STRING" % basejson[value]) return basejson[value], False else: basejson = basejson[value] @@ -1332,15 +1413,14 @@ class AppBase: print(f"[INFO] LOADING STRING '%s' AS JSON" % basejson[value]) try: basejson = json.loads(basejson[value]) - print("BASEJSON: %s" % basejson) + print("[DEBUG] BASEJSON: %s" % basejson) except json.decoder.JSONDecodeError as e: - print("RETURNING BECAUSE '%s' IS A NORMAL STRING" % basejson[value]) + print("[DEBUG] RETURNING BECAUSE '%s' IS A NORMAL STRING (1)" % basejson[value]) return basejson[value], False else: basejson = basejson[value] - #print("Parsed BASEJSON: %s" % basejson) outercnt += 1 except KeyError as e: @@ -1385,21 +1465,19 @@ class AppBase: if actionname_lower == "exec" or actionname_lower == "webhook" or actionname_lower == "schedule" or actionname_lower == "userinput" or actionname_lower == "email_trigger" or actionname_lower == "trigger": baseresult = execution_data["execution_argument"] elif actionname_lower == "shuffle_cache": - print("SHOULD GET CACHE KEY: %s" % parsersplit) + print("[DEBUG] SHOULD GET CACHE KEY: %s" % parsersplit) if len(parsersplit) > 1: actual_key = parsersplit[1] - print("KEY: %s" % actual_key) + print("[DEBUG] KEY: %s" % actual_key) cachedata = self.get_cache(actual_key) print("CACHE: %s" % cachedata) parsersplit.pop(1) try: baseresult = json.dumps(cachedata) except json.decoder.JSONDecodeError as e: - print("Failed json dumping: %s" % e) + print("[WARNING] Failed json dumping: %s" % e) - #returndata = str(baseresult)+str(appendresult) else: - #print("Within execution data check. Execution data: %s", execution_data["results"]) if execution_data["results"] != None: for result in execution_data["results"]: resultlabel = result["action"]["label"].replace(" ", "_", -1).lower() @@ -1407,19 +1485,19 @@ class AppBase: baseresult = result["result"] break else: - print("No results to get values from.") + print("[DEBUG] No results to get values from.") baseresult = "$" + parsersplit[0][1:] - print("BEFORE VARIABLES!") + print("[DEBUG] BEFORE VARIABLES!") if len(baseresult) == 0: try: - #print("WF Variables: %s" % execution_data["workflow"]["workflow_variables"]) for variable in execution_data["workflow"]["workflow_variables"]: variablename = variable["name"].replace(" ", "_", -1).lower() if variablename.lower() == actionname_lower: baseresult = variable["value"] break + except KeyError as e: print("[INFO] KeyError wf variables: %s" % e) pass @@ -1427,10 +1505,9 @@ class AppBase: print("[INFO] TypeError wf variables: %s" % e) pass - print("BEFORE EXECUTION VAR") + print("[DEBUG] BEFORE EXECUTION VAR") if len(baseresult) == 0: try: - #print("Execution Variables: %s" % execution_data["execution_variables"]) for variable in execution_data["execution_variables"]: variablename = variable["name"].replace(" ", "_", -1).lower() if variablename.lower() == actionname_lower: @@ -1444,7 +1521,7 @@ class AppBase: pass except KeyError as error: - print(f"KeyError in JSON: {error}") + print(f"[DEBUG] KeyError in JSON: {error}") print(f"[INFO] After first trycatch. Baseresult")#, baseresult) @@ -1455,7 +1532,7 @@ class AppBase: print("[INFO] After second return") if len(parsersplit) == 1: returndata = str(baseresult)+str(appendresult) - print("RETURNING!")#: %s" % returndata) + print("[DEBUG] RETURNING!")#: %s" % returndata) return returndata, False baseresult = baseresult.replace(" True,", " true,") @@ -1481,26 +1558,26 @@ class AppBase: try: parseditem = json.dumps(parseditem) except json.decoder.JSONDecodeError as e: - print("Parseditem issue: %s" % e) + print("[WARNING] Parseditem issue: %s" % e) pass - print("DATA: (%s) %s" % (type(data), data)) + print("[DEBUG] DATA: (%s) %s" % (type(data), data)) if is_loop: - print("DATA IS A LOOP - SHOULD WRAP") + print("[DEBUG] DATA IS A LOOP - SHOULD WRAP") if parsersplit[-1] == "#": - print("SET DATA WRAPPER TO NORMAL!") + print("[WARNING] SET DATA WRAPPER TO NORMAL!") parseditem = "${SHUFFLE_NO_SPLITTER%s}$" % json.dumps(data) else: # Return value: ${id[12345, 45678]}$ - print("SET DATA WRAPPER TO %s!" % parsersplit[-1]) + print("[WARNING] SET DATA WRAPPER TO %s!" % parsersplit[-1]) parseditem = "${%s%s}$" % (parsersplit[-1], json.dumps(data)) - print("Before last return with %s" % appendresult) + print("[DEBUG] Before last return with %s" % appendresult) returndata = str(parseditem)+str(appendresult) # New in 0.8.97: Don't return items without lists - print("RETURNDATA: %s" % returndata) + #self.logger.info("RETURNDATA: %s" % returndata) #return returndata, is_loop try: return json.dumps(json.loads(returndata)), is_loop @@ -1508,8 +1585,128 @@ class AppBase: print("Error in decoder: %s" % e) return returndata, is_loop + # Sending self as it's not a normal function + def parse_liquid(template, self): + + #self.logger.info("Inside liquid with glob: %s" % globals()) + try: + if len(template) > 5000000: + self.logger.info("[DEBUG] Skipping liquid - size too big (%d)" % len(template)) + return template + + #if not "{{" in template or not "}}" in template: + # if not "{%" in template or not "%}" in template: + # self.logger.info("Skipping liquid - missing {{ }} and {% %}") + # return template + + #if not "{{" in template or not "}}" in template: + # return template + + #self.logger.info(globals()) + self.logger.info("[DEBUG] Running liquid with data of length %d" % len(template)) + run = Liquid(template, mode="wild", from_file=False) + + # Can't handle self yet (?) + ret = run.render(**globals()) + return ret + #try: + #run = Liquid(template) + #return ret + #except liquid.exceptions.LiquidSyntaxError as e: + # run = Liquid(template, {'mode': 'python'}) + # ret = run.render(**globals()) + # return ret + #except liquid.exceptions.LiquidRenderError as e: + # self.logger.info("Render error: %s" % e) + except jinja2.exceptions.TemplateNotFound as e: + self.logger.info("[ERROR] Template error: %s" % e) + except jinja2.exceptions.TemplateSyntaxError as e: + self.logger.info("[ERROR] Syntax error: %s" % e) + except: + self.logger.info("[ERROR] General exception for liquid") + + return template + + # Suboptimal cleanup script for BOdy parsing of OpenAPI + # Should have a regex which looks for the value, then goes out and cleans up the key + def recurse_cleanup_script(data): + deletekeys = [] + newvalue = data + try: + if not isinstance(data, dict): + newvalue = json.loads(data) + else: + newvalue = data + + for key, value in newvalue.items(): + if isinstance(value, str) and len(value) == 0: + deletekeys.append(key) + continue + + if isinstance(value, list): + try: + value = json.dumps(value) + except: + print("[WARNING] Json parsing issue in recursed value") + pass + + if value == "${%s}" % key: + print("[WARNING] Deleting %s because key = value" % key) + deletekeys.append(key) + continue + elif "${" in value and "}" in value: + print("[WARNING] Deleting %s because it contains ${ and }" % key) + deletekeys.append(key) + continue + + if isinstance(value, dict): + newvalue[key] = recurse_cleanup_script(value) + + except json.decoder.JSONDecodeError as e: + print(f"[WARNING] Failed JSON replacement for OpenAPI keys (3) {e}") + except Exception as e: + print(f"[WARNING] Failed as an exception (1): {e}") + + try: + for deletekey in deletekeys: + try: + del newvalue[deletekey] + except: + pass + except Exception as e: + print(f"[WARNING] Failed in deletekeys: {e}") + return data + + try: + for key, value in newvalue.items(): + if isinstance(value, bool): + continue + elif isinstance(value, dict) and not bool(value): + continue + + try: + value = json.loads(value) + newvalue[key] = value + except json.decoder.JSONDecodeError as e: + continue + except Exception as e: + continue + + try: + data = json.dumps(newvalue) + except json.decoder.JSONDecodeError as e: + print("[WARNING] JsonDecodeError: %s" % e) + data = newvalue + + except json.decoder.JSONDecodeError as e: + print("[WARNING] Failed JSON replacement for OpenAPI keys (2) {e}") + except Exception as e: + print(f"[WARNING] Failed as an exception (2): {e}") + + return data + # Parses parameters sent to it and returns whether it did it successfully with the values found - def parse_params(action, fullexecution, parameter): + def parse_params(action, fullexecution, parameter, self): # Skip if it starts with $? jsonparsevalue = "$." is_loop = False @@ -1517,7 +1714,22 @@ class AppBase: # Matches with space in the first part, but not in subsequent parts. # JSON / yaml etc shouldn't have spaces in their fields anyway. #match = ".*?([$]{1}([a-zA-Z0-9 _-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,})[$/, ]?" - match = ".*?([$]{1}([a-zA-Z0-9 _-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,})" + #match = ".*?([$]{1}([a-zA-Z0-9 _-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,})" + + match = ".*?([$]{1}([a-zA-Z0-9_-]+\.?){1}([a-zA-Z0-9#_-]+\.?){0,})" # Removed space - no longer ok. Force underscore. + + # Extra replacements for certain scenarios + escaped_dollar = "\\$" + escape_replacement = "\\%\\%\\%\\%\\%" + end_variable = "^_^" + + #self.logger.info("Input value: %s" % parameter["value"]) + try: + parameter["value"] = parameter["value"].replace(escaped_dollar, escape_replacement, -1) + except: + self.logger.info("Error in initial replacement of escaped dollar!") + + #self.logger.info("POST input value: %s" % parameter["value"]) # Regex to find all the things if parameter["variant"] == "STATIC_VALUE": @@ -1526,7 +1738,7 @@ class AppBase: #self.logger.debug(f"\n\nHandle static data with JSON: {data}\n\n") #self.logger.info("STATIC PARSED: %s" % actualitem) if len(actualitem) > 0: - print("ACTUAL: ", actualitem) + #self.logger.info("[DEACTUAL: ", actualitem) for replace in actualitem: try: to_be_replaced = replace[0] @@ -1538,8 +1750,8 @@ class AppBase: # Trying without string dumping. value, is_loop = get_json_value(fullexecution, to_be_replaced) - #print("\n\nType of value: %s. Value: %s" % (type(value), value)) - print("\n\nType of value: %s" % type(value)) + #self.logger.info("\n\nType of value: %s. Value: %s" % (type(value), value)) + self.logger.info("\n\nType of value: %s" % type(value)) if isinstance(value, str): parameter["value"] = parameter["value"].replace(to_be_replaced, value) elif isinstance(value, dict) or isinstance(value, list): @@ -1551,18 +1763,18 @@ class AppBase: # parameter["value"] = parameter["value"].replace(to_be_replaced, str(value)) #except: # parameter["value"] = parameter["value"].replace(to_be_replaced, json.dumps(value)) - # print("Failed parsing value as string?") + # self.logger.info("Failed parsing value as string?") else: - print("Unknown type %s" % type(value)) + self.logger.info("Unknown type %s" % type(value)) try: parameter["value"] = parameter["value"].replace(to_be_replaced, json.dumps(value)) except json.decoder.JSONDecodeError as e: parameter["value"] = parameter["value"].replace(to_be_replaced, value) - #print("VALUE: %s" % parameter["value"]) + #self.logger.info("VALUE: %s" % parameter["value"]) if parameter["variant"] == "WORKFLOW_VARIABLE": - print("Handling workflow variable") + self.logger.info("[DEBUG] Handling workflow variable") found = False try: for item in fullexecution["workflow"]["workflow_variables"]: @@ -1571,10 +1783,10 @@ class AppBase: parameter["value"] = item["value"] break except KeyError as e: - print("KeyError WF variable 1: %s" % e) + self.logger.info("KeyError WF variable 1: %s" % e) pass except TypeError as e: - print("TypeError WF variables 1: %s" % e) + self.logger.info("TypeError WF variables 1: %s" % e) pass if not found: @@ -1584,10 +1796,10 @@ class AppBase: parameter["value"] = item["value"] break except KeyError as e: - print("KeyError WF variable 2: %s" % e) + self.logger.info("KeyError WF variable 2: %s" % e) pass except TypeError as e: - print("TypeError WF variables 2: %s" % e) + self.logger.info("TypeError WF variables 2: %s" % e) pass elif parameter["variant"] == "ACTION_RESULT": @@ -1620,29 +1832,48 @@ class AppBase: try: to_be_replaced = replace[0] except IndexError: - print("Nothing to replace?: " % e) + self.logger.info("Nothing to replace?: " % e) continue # This will never be a loop aka multi argument parameter["value"] = to_be_replaced value, is_loop = get_json_value(fullexecution, to_be_replaced) - print("Loop: %s" % is_loop) + self.logger.info("Loop: %s" % is_loop) if isinstance(value, str): parameter["value"] = parameter["value"].replace(to_be_replaced, value) elif isinstance(value, dict): parameter["value"] = parameter["value"].replace(to_be_replaced, json.dumps(value)) else: - print("Unknown type %s" % type(value)) + self.logger.info("Unknown type %s" % type(value)) try: parameter["value"] = parameter["value"].replace(to_be_replaced, json.dumps(value)) except json.decoder.JSONDecodeError as e: parameter["value"] = parameter["value"].replace(to_be_replaced, value) + #self.logger.info("PRE Replaced data: %s" % parameter["value"]) + + try: + parameter["value"] = parameter["value"].replace(end_variable, "", -1) + parameter["value"] = parameter["value"].replace(escape_replacement, "$", -1) + except: + self.logger.info("Error in datareplacement") + + # Just here in case it breaks + # Implemented 02.08.2021 + #self.logger.info("Pre liquid: %s" % parameter["value"]) + try: + parameter["value"] = parse_liquid(parameter["value"], self) + except: + pass + + #self.logger.info("Replaced data: %s" % parameter["value"]) + + #self.logger.info("POST liquid: %s" % parameter["value"]) return "", parameter["value"], is_loop def run_validation(sourcevalue, check, destinationvalue): - self.logger.info("Checking %s %s %s" % (sourcevalue, check, destinationvalue)) + print("[DEBUG] Checking %s %s %s" % (sourcevalue, check, destinationvalue)) if check == "=" or check.lower() == "equals": if str(sourcevalue).lower() == str(destinationvalue).lower(): @@ -1659,6 +1890,29 @@ class AppBase: elif check.lower() == "contains": if destinationvalue.lower() in sourcevalue.lower(): return True + + elif check.lower() == "is empty": + if len(sourcevalue) == 0: + return True + + if str(sourcevalue) == 0: + return True + + return False + #if tmp == "[]": + # tmp = [] + + #if type(tmp) == list and len(tmp) == 0 and not flip: + # new_list.append(item) + #elif type(tmp) == list and len(tmp) > 0 and flip: + # new_list.append(item) + #elif type(tmp) == str and not tmp and not flip: + # new_list.append(item) + #elif type(tmp) == str and tmp and flip: + # new_list.append(item) + #else: + # failed_list.append(item) + elif check.lower() == "contains_any_of": newvalue = [destinationvalue.lower()] if "," in destinationvalue: @@ -1675,30 +1929,41 @@ class AppBase: return True return False - elif check.lower() == "larger than": + elif check.lower() == "larger than" or check.lower() == "bigger than": try: if str(sourcevalue).isdigit() and str(destinationvalue).isdigit(): if int(sourcevalue) > int(destinationvalue): return True except AttributeError as e: - self.logger.error("[WARNING] Condition larger than failed with values %s and %s: %s" % (sourcevalue, destinationvalue, e)) + print("[WARNING] Condition larger than failed with values %s and %s: %s" % (sourcevalue, destinationvalue, e)) return False - elif check.lower() == "smaller than": + elif check.lower() == "smaller than" or check.lower() == "less than": try: if str(sourcevalue).isdigit() and str(destinationvalue).isdigit(): if int(sourcevalue) < int(destinationvalue): return True except AttributeError as e: - self.logger.error("[WARNING] Condition smaller than failed with values %s and %s: %s" % (sourcevalue, destinationvalue, e)) + print("[WARNING] Condition smaller than failed with values %s and %s: %s" % (sourcevalue, destinationvalue, e)) return False + elif check.lower() == "re" or check.lower() == "matches regex": + try: + found = re.search(destinationvalue, sourcevalue) + except re.error as e: + print("[WARNING] Regex error in condition: %s" % e) + return False + + if found == None: + return False + + return True else: - self.logger.info("Condition: can't handle %s yet. Setting to true" % check) + print("[DEBUG] Condition: can't handle %s yet. Setting to true" % check) return False - def check_branch_conditions(action, fullexecution): + def check_branch_conditions(action, fullexecution, self): # relevantbranches = workflow.branches where destination = action try: if fullexecution["workflow"]["branches"] == None or len(fullexecution["workflow"]["branches"]) == 0: @@ -1726,21 +1991,20 @@ class AppBase: # Parse all values first here sourcevalue = condition["source"]["value"] - check, sourcevalue, is_loop = parse_params(action, fullexecution, condition["source"]) + check, sourcevalue, is_loop = parse_params(action, fullexecution, condition["source"], self) if check: - return False, {"success": False, "reason": "Failed condition: %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check)} - + return False, {"success": False, "reason": "Failed condition (1): %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check)} #sourcevalue = sourcevalue.encode("utf-8") - sourcevalue = parse_wrapper_start(sourcevalue) + sourcevalue = parse_wrapper_start(sourcevalue, self) destinationvalue = condition["destination"]["value"] - check, destinationvalue, is_loop = parse_params(action, fullexecution, condition["destination"]) + check, destinationvalue, is_loop = parse_params(action, fullexecution, condition["destination"], self) if check: - return False, {"success": False, "reason": "Failed condition: %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check)} + return False, {"success": False, "reason": "Failed condition (2): %s %s %s because %s" % (sourcevalue, condition["condition"]["value"], destinationvalue, check)} #destinationvalue = destinationvalue.encode("utf-8") - destinationvalue = parse_wrapper_start(destinationvalue) + destinationvalue = parse_wrapper_start(destinationvalue, self) available_checks = [ "=", "equals", @@ -1764,7 +2028,7 @@ class AppBase: self.logger.warning("Skipping %s %s %s because %s is invalid." % (sourcevalue, condition["condition"]["value"], destinationvalue, condition["condition"]["value"])) continue - #print(destinationvalue) + #self.logger.info(destinationvalue) # NEGATE # Configuration = negated because of WorkflowAppActionParam.. @@ -1777,7 +2041,7 @@ class AppBase: if not validation: self.logger.info("Failed condition check for %s %s %s." % (sourcevalue, condition["condition"]["value"], destinationvalue)) - return False, {"success": False, "reason": "Failed condition: %s %s %s" % (sourcevalue, condition["condition"]["value"], destinationvalue)} + return False, {"success": False, "reason": "Failed condition (3): %s %s %s" % (sourcevalue, condition["condition"]["value"], destinationvalue)} # Make a general parser here, at least to get param["name"] = param["value"] in maparameter[string]string @@ -1789,28 +2053,28 @@ class AppBase: # THE START IS ACTUALLY RIGHT HERE :O # Checks whether conditions are met, otherwise set - branchcheck, tmpresult = check_branch_conditions(action, fullexecution) + branchcheck, tmpresult = check_branch_conditions(action, fullexecution, self) if isinstance(tmpresult, object) or isinstance(tmpresult, list): - print("Fixing branch return as object -> string") + self.logger.info("[DEBUG] Fixing branch return as object -> string") try: #tmpresult = tmpresult.replace("'", "\"") tmpresult = json.dumps(tmpresult) except json.decoder.JSONDecodeError as e: - print(f"[WARNING] Failed condition parsing {tmpresult} to string") + self.logger.info(f"[WARNING] Failed condition parsing {tmpresult} to string") if not branchcheck: self.logger.info("Failed one or more branch conditions.") - action_result["result"] = tmpresult - action_result["status"] = "SKIPPED" + self.action_result["result"] = tmpresult + self.action_result["status"] = "SKIPPED" try: - ret = requests.post("%s%s" % (self.base_url, stream_path), headers=headers, json=action_result) + ret = requests.post("%s%s" % (self.base_url, stream_path), headers=headers, json=self.action_result) self.logger.info("Result: %d" % ret.status_code) if ret.status_code != 200: self.logger.info(ret.text) except requests.exceptions.ConnectionError as e: self.logger.exception(e) - print("\n\nRETURNING BECAUSE A BRANCH FAILED: %s\n\n" % tmpresult) + self.logger.info("\n\n[DEBUG] RETURNING BECAUSE A BRANCH FAILED: %s\n\n" % tmpresult) return # Replace name cus there might be issues @@ -1828,8 +2092,8 @@ class AppBase: func = getattr(self, actionname, None) if func == None: self.logger.debug(f"Failed executing {actionname} because func is None.") - action_result["status"] = "FAILURE" - action_result["result"] = "Function %s doesn't exist." % actionname + self.action_result["status"] = "FAILURE" + self.action_result["result"] = "Function %s doesn't exist." % actionname elif callable(func): try: if len(action["parameters"]) < 1: @@ -1838,16 +2102,16 @@ class AppBase: # Potentially parse JSON here # FIXME - add potential authentication as first parameter(s) here # params[parameter["name"]] = parameter["value"] - #print(fullexecution["authentication"] + #self.logger.info(fullexecution["authentication"] # What variables are necessary here tho hmm params = {} try: for item in action["authentication"]: - #print("AUTH: ", key, value) + #self.logger.info("AUTH: ", key, value) params[item["key"]] = item["value"] except KeyError: - print("No authentication specified!") + self.logger.info("[DEBUG] No authentication specified!") pass #action["authentication"] @@ -1861,16 +2125,16 @@ class AppBase: # Hack for key:value in options using || try: if parameter["options"] != None and len(parameter["options"]) > 0: - #print(f'OPTIONS: {parameter["options"]}') - #print(f'OPTIONS VAL: {parameter}') + #self.logger.info(f'OPTIONS: {parameter["options"]}') + #self.logger.info(f'OPTIONS VAL: {parameter}') if "||" in parameter["value"]: splitvalue = parameter["value"].split("||") if len(splitvalue) > 1: - #print(f'[INFO] Parsed split || options of actions["parameters"]["name"]') + #self.logger.info(f'[INFO] Parsed split || options of actions["parameters"]["name"]') action["parameters"][counter]["value"] = splitvalue[1] except (IndexError, KeyError, TypeError) as e: - print("[WARNING] Options err: {e}") + self.logger.info("[WARNING] Options err: {e}") # This part is purely for OpenAPI accessibility. # It replaces the data back into the main item @@ -1878,7 +2142,7 @@ class AppBase: # but this has changed to do lists within items and such if parameter["name"] == "body": bodyindex = counter - #print("PARAM: %s" % parameter) + #self.logger.info("PARAM: %s" % parameter) try: values = parameter["value_replace"] if values != None: @@ -1887,62 +2151,47 @@ class AppBase: replace_value = val["value"] replace_key = val["key"] if (val["value"].startswith("{") and val["value"].endswith("}")) or (val["value"].startswith("[") and val["value"].endswith("]")): - print(f"""Trying to parse as JSON: {val["value"]}""") + self.logger.info(f"""Trying to parse as JSON: {val["value"]}""") try: value_replace = json.loads(val["value"]) # If it gets here, remove the "" infront and behind the key as well since this is preventing the JSON from being loaded replace_key = f"\"{replace_key}\"" except json.decoder.JSONDecodeError as e: - print("Failed JSON replacement for OpenAPI %s", val["key"]) + self.logger.info("Failed JSON replacement for OpenAPI %s", val["key"]) elif val["value"].lower() == "true" or val["value"].lower() == "false": replace_key = f"\"{replace_key}\"" action["parameters"][counter]["value"] = action["parameters"][counter]["value"].replace(replace_key, replace_value, 1) - print(f'[INFO] Added param {val["key"]} for body (using OpenAPI)') + self.logger.info(f'[INFO] Added param {val["key"]} for body (using OpenAPI)') added += 1 #action["parameters"]["body"] - print("ADDED %d parameters for body" % added) + self.logger.info("ADDED %d parameters for body" % added) except KeyError as e: - print("KeyError body OpenAPI: %s" % e) + self.logger.info("KeyError body OpenAPI: %s" % e) pass - try: - newvalue = json.loads(action["parameters"][counter]["value"]) - deletekeys = [] - for key, value in newvalue.items(): - if isinstance(value, str) and len(value) == 0: - deletekeys.append(key) - continue + + self.logger.info(f"""HANDLING {action["parameters"][counter]["value"]}""") + action["parameters"][counter]["value"] = recurse_cleanup_script(action["parameters"][counter]["value"]) - for deletekey in deletekeys: - del newvalue[deletekey] + #self.logger.info(action["parameters"]) - action["parameters"][counter]["value"] = json.dumps(newvalue) - - except json.decoder.JSONDecodeError as e: - print("Failed JSON replacement for OpenAPI keys (2) {e}") - - #if "\n" in action["parameters"][counter]["value"]: - # print("MODIFYING BODY!!") - # newbody = "" - # for line in action["parameters"][counter]["value"].split("\n"): - # if ": \"\"" in line: - # print("Skipping line %s" % line) - # continue - - # newbody += line - - # print("New body: %s" % newbody) - - break - - #print(action["parameters"]) + # This seems redundant now + self.logger.info("[DEBUG] Pre parameters") for parameter in newparams: action["parameters"].append(parameter) + self.action = action + + # Setting due to them being overwritten, but still later useful + try: + self.original_action = json.loads(json.dumps(action)) + except Exception as e: + self.logger.info(f"[ERROR] Failed parsing action as JSON to original action. This COULD have bad effects on LOOPED executions: {e}") + # calltimes is used to handle forloops in the app itself. # 2 kinds of loop - one in gui with one app each, and one like this, # which is super fast, but has a bad overview (potentially good tho) @@ -1953,45 +2202,46 @@ class AppBase: # Multi_parameter has the data for each. variable minlength = 0 + self.logger.info("[DEBUG] Pre-loading parameters") multi_parameters = json.loads(json.dumps(params)) multiexecution = False multi_execution_lists = [] remove_params = [] for parameter in action["parameters"]: - check, value, is_loop = parse_params(action, fullexecution, parameter) + check, value, is_loop = parse_params(action, fullexecution, parameter, self) if check: raise "Value check error: %s" % Exception(check) # Custom format for ${name[0,1,2,...]}$ #submatch = "([${]{2}([0-9a-zA-Z_-]+)(\[.*\])[}$]{2})" - #print(f"Returnedvalue: {value}") + #self.logger.info(f"Returnedvalue: {value}") # OLD: Used until 13.03.2021: submatch = "([${]{2}#?([0-9a-zA-Z_-]+)#?(\[.*\])[}$]{2})" # \${[0-9a-zA-Z_-]+#?(\[.*?]}\$) submatch = "([${]{2}#?([0-9a-zA-Z_-]+)#?(\[.*?]}\$))" actualitem = re.findall(submatch, value, re.MULTILINE) try: if action["skip_multicheck"]: - print("Skipping multicheck") + self.logger.info("Skipping multicheck") actualitem = [] except KeyError: pass - #print("Return value: %s" % value) + #self.logger.info("Return value: %s" % value) actionname = action["name"] - #print("Multicheck ", actualitem) - #print("ITEM LENGTH: %d, Actual item: %s" % (len(actualitem), actualitem)) + #self.logger.info("Multicheck ", actualitem) + #self.logger.info("ITEM LENGTH: %d, Actual item: %s" % (len(actualitem), actualitem)) if len(actualitem) > 0: multiexecution = True # Loop WITHOUT JSON variables go here. # Loop WITH variables go in else. - print("Before first part in multiexec!") + self.logger.info("Before first part in multiexec!") handled = False # Has a loop without a variable used inside if len(actualitem[0]) > 2 and actualitem[0][1] == "SHUFFLE_NO_SPLITTER": - print("(1) Pre replacement: %s" % actualitem[0][2]) + self.logger.info("(1) Pre replacement: %s" % actualitem[0][2]) tmpitem = value index = 0 @@ -2002,11 +2252,11 @@ class AppBase: if replacement.startswith("\"") and replacement.endswith("\""): replacement = replacement[1:len(replacement)-1] - print("POST replacement: %s" % replacement) + self.logger.info("POST replacement: %s" % replacement) #json_replacement = tmpitem.replace(actualitem[index][0], replacement, 1) - #print("AFTER POST replacement: %s" % json_replacement) - #json_replacement = replacement + #self.logger.info("AFTER POST replacement: %s" % json_replacement) + json_replacement = replacement try: json_replacement = json.loads(replacement) except json.decoder.JSONDecodeError as e: @@ -2014,30 +2264,30 @@ class AppBase: replacement = replacement.replace("\'", "\"", -1) json_replacement = json.loads(replacement) except: - print("JSON error singular: %s" % e) + self.logger.info("JSON error singular: %s" % e) if len(json_replacement) > minlength: minlength = len(json_replacement) - print("PRE new_replacement") + self.logger.info("PRE new_replacement") new_replacement = [] for i in range(len(json_replacement)): if isinstance(json_replacement[i], dict) or isinstance(json_replacement[i], list): tmp_replacer = json.dumps(json_replacement[i]) - newvalue = tmpitem.replace(actualitem[index][0], tmp_replacer, 1) + newvalue = tmpitem.replace(str(actualitem[index][0]), str(tmp_replacer), 1) else: - newvalue = tmpitem.replace(actualitem[index][0], json_replacement[i], 1) + newvalue = tmpitem.replace(str(actualitem[index][0]), str(json_replacement[i]), 1) try: newvalue = json.loads(newvalue) except json.decoder.JSONDecodeError as e: - print("DECODER ERROR: %s" % e) + self.logger.info("DECODER ERROR: %s" % e) pass new_replacement.append(newvalue) - print("New replacement: %s" % new_replacement) + self.logger.info("New replacement: %s" % new_replacement) # New tmpitem = tmpitem.replace(actualitem[index][0], replacement, 1) @@ -2046,47 +2296,47 @@ class AppBase: resultarray = [] isfile = False try: - print("(1) ------------ PARAM: %s" % parameter["schema"]["type"]) + self.logger.info("(1) ------------ PARAM: %s" % parameter["schema"]["type"]) if parameter["schema"]["type"] == "file" and len(value) > 0: - print("(1) SHOULD HANDLE FILE IN MULTI. Get based on value %s" % tmpitem) + self.logger.info("(1) SHOULD HANDLE FILE IN MULTI. Get based on value %s" % tmpitem) # This is silly :) # Q: Is there something wrong with the download system? # It seems to return "FILE CONTENT: %s" with the ID as %s for tmp_file_split in json.loads(tmpitem): - print("(1) PRE GET FILE %s" % tmp_file_split) + self.logger.info("(1) PRE GET FILE %s" % tmp_file_split) file_value = self.get_file(tmp_file_split) - print("(1) POST AWAIT %s" % file_value) + self.logger.info("(1) POST AWAIT %s" % file_value) resultarray.append(file_value) - print("(1) FILE VALUE FOR VAL %s: %s" % (tmp_file_split, file_value)) + self.logger.info("(1) FILE VALUE FOR VAL %s: %s" % (tmp_file_split, file_value)) isfile = True except NameError as e: - print("(1) SCHEMA NAMEERROR IN FILE HANDLING: %s" % e) + self.logger.info("(1) SCHEMA NAMEERROR IN FILE HANDLING: %s" % e) except KeyError as e: - print("(1) SCHEMA KEYERROR IN FILE HANDLING: %s" % e) + self.logger.info("(1) SCHEMA KEYERROR IN FILE HANDLING: %s" % e) except json.decoder.JSONDecodeError as e: - print("(1) JSON ERROR IN FILE HANDLING: %s" % e) + self.logger.info("(1) JSON ERROR IN FILE HANDLING: %s" % e) if not isfile: - print("Resultarray (NOT FILE): %s" % resultarray) + self.logger.info("Resultarray (NOT FILE): %s" % resultarray) params[parameter["name"]] = tmpitem multi_parameters[parameter["name"]] = new_replacement else: - print("Resultarray (FILE): %s" % resultarray) + self.logger.info("Resultarray (FILE): %s" % resultarray) params[parameter["name"]] = resultarray multi_parameters[parameter["name"]] = resultarray #if len(resultarray) == 0: - # print("[WARNING] Returning empty array because the array length to be looped is 0 (1)") + # self.logger.info("[WARNING] Returning empty array because the array length to be looped is 0 (1)") # action_result["status"] = "SUCCESS" # action_result["result"] = "[]" # self.send_result(action_result, headers, stream_path) # return multi_execution_lists.append(new_replacement) - #print("MULTI finished: %s" % json_replacement) + #self.logger.info("MULTI finished: %s" % json_replacement) else: - print(f"(2) Pre replacement (loop with variables). Variables: {actualitem}") #% actualitem) + self.logger.info(f"(2) Pre replacement (loop with variables). Variables: {actualitem}") #% actualitem) # This is here to handle for loops within variables.. kindof # 1. Find the length of the longest array # 2. Build an array with the base values based on parameter["value"] @@ -2100,12 +2350,14 @@ class AppBase: actualitem = replace[2] if actualitem.endswith("}$"): actualitem = actualitem[:-2] + except IndexError: + self.logger.info("[WARNING] Indexerror") continue - #print(f"\n\nTMPITEM: {actualitem}\n\n") + #self.logger.info(f"\n\nTMPITEM: {actualitem}\n\n") #actualitem = parse_wrapper_start(actualitem) - #print(f"\n\nTMPITEM2: {actualitem}\n\n") + #self.logger.info(f"\n\nTMPITEM2: {actualitem}\n\n") try: itemlist = json.loads(actualitem) @@ -2116,7 +2368,7 @@ class AppBase: curminlength = len(itemlist) except json.decoder.JSONDecodeError as e: - print("JSON Error (replace): %s in %s" % (e, actualitem)) + self.logger.info("JSON Error (replace): %s in %s" % (e, actualitem)) replacements[to_be_replaced] = actualitem @@ -2124,7 +2376,7 @@ class AppBase: # Parses the data as string with length, split etc. before moving on. - #print("In second part of else: %s" % (len(itemlist))) + #self.logger.info("In second part of else: %s" % (len(itemlist))) # This is a result array for JUST this value.. # What if there are more? resultarray = [] @@ -2136,128 +2388,81 @@ class AppBase: replacement = replacement[1:len(replacement)-1] #except json.decoder.JSONDecodeError as e: - #print("REPLACING %s with %s" % (key, replacement)) + #self.logger.info("REPLACING %s with %s" % (key, replacement)) #replacement = parse_wrapper_start(replacement) tmpitem = tmpitem.replace(key, replacement, -1) # This code handles files. - print("(2) ------------ PARAM: %s" % parameter["schema"]["type"]) + self.logger.info("(2) ------------ PARAM: %s" % parameter["schema"]["type"]) isfile = False try: if parameter["schema"]["type"] == "file" and len(value) > 0: - print("(2) SHOULD HANDLE FILE IN MULTI. Get based on value %s" % parameter["value"]) + self.logger.info("(2) SHOULD HANDLE FILE IN MULTI. Get based on value %s" % parameter["value"]) for tmp_file_split in json.loads(parameter["value"]): - print("(2) PRE GET FILE %s" % tmp_file_split) + self.logger.info("(2) PRE GET FILE %s" % tmp_file_split) file_value = self.get_file(tmp_file_split) - print("(2) POST AWAIT %s" % file_value) + self.logger.info("(2) POST AWAIT %s" % file_value) resultarray.append(file_value) - print("(2) FILE VALUE FOR VAL %s: %s" % (tmp_file_split, file_value)) + self.logger.info("(2) FILE VALUE FOR VAL %s: %s" % (tmp_file_split, file_value)) isfile = True except KeyError as e: - print("(2) SCHEMA ERROR IN FILE HANDLING: %s" % e) + self.logger.info("(2) SCHEMA ERROR IN FILE HANDLING: %s" % e) except json.decoder.JSONDecodeError as e: - print("(2) JSON ERROR IN FILE HANDLING: %s" % e) + self.logger.info("(2) JSON ERROR IN FILE HANDLING: %s" % e) if not isfile: + tmpitem = tmpitem.replace("\\\\", "\\", -1) resultarray.append(tmpitem) # With this parameter ready, add it to... a greater list of parameters. Rofl - print("LENGTH OF ARR: %d" % len(resultarray)) + self.logger.info("LENGTH OF ARR: %d" % len(resultarray)) if len(resultarray) == 0: - print("[WARNING] Returning empty array because the array length to be looped is 0 (0)") - action_result["status"] = "SUCCESS" - action_result["result"] = "[]" - self.send_result(action_result, headers, stream_path) + self.logger.info("[WARNING] Returning empty array because the array length to be looped is 0 (0)") + self.action_result["status"] = "SUCCESS" + self.action_result["result"] = "[]" + self.send_result(self.action_result, headers, stream_path) return - #print("RESULTARRAY: %s" % resultarray) + #self.logger.info("RESULTARRAY: %s" % resultarray) if resultarray not in multi_execution_lists: multi_execution_lists.append(resultarray) multi_parameters[parameter["name"]] = resultarray - - #if parameter["id"] == "body_replacement": - # print("Should run body MULTI replacement in index %d with %s" % (bodyindex, parameter)) - # try: - # print("PREBODY: %s" % params["body"]) - - # parsedarray = str(resultarray) - # try: - # parsedarray = json.dumps(resultarray) - # except: - # pass - - # if f'\"{parameter["name"]}\"' in params["body"]: - # params["body"] = params["body"].replace(f'\"{parameter["name"]}\"' , parsedarray, -1) - # multi_parameters["body"] = multi_parameters["body"].replace(f'\"{parameter["name"]}\"' , parsedarray, -1) - # else: - # params["body"] = params["body"].replace(parameter["name"], parsedarray, -1) - # multi_parameters["body"] = multi_parameters["body"].replace(parameter["name"], parsedarray, -1) - - # #print("POSTBODY: %s" % params["body"]) - # #if isinstance(multi_parameters, list): - # # print("MULTIPARAM AS LIST (NOT REPLACING!!)!") - # # for multiparam in multi_parameters: - # # print(f"MULTIPARAM: {multiparam}") - # # #multi_parameters["body"] = multi_parameters["body"].replace(parameter["name"], str(parameter["value"]), -1) - # #else: - - # except KeyError as e: - # print("KEYERROR: %s" % e) - - # remove_params.append(parameter["name"]) - # #bodyindex = counter - # continue - else: # Parses things like int(value) - print("Normal parsing (not looping)")#with data %s" % value) - value = parse_wrapper_start(value) + self.logger.info("[DEBUG] Normal parsing (not looping)")#with data %s" % value) + # This part has fucked over so many random JSON usages because of weird paranthesis parsing - #if parameter["id"] == "body_replacement": - # print("Should run body replacement in index %d with %s" % (bodyindex, parameter)) - # try: - # print("PREBODY: %s" % params["body"]) - # params["body"] = params["body"].replace(parameter["name"], parameter["value"], -1) - # print("POSTBODY: %s" % params["body"]) - # except KeyError as e: - # print("KEYERROR: %s" % e) + value = parse_wrapper_start(value, self) + self.logger.info("[DEBUG] Post return: %s" % value) - # #bodyindex = counter - # continue - - #for parameter in action["parameters"]: - #if parameter["name"] == "body": - # print("PARAM: %s" % parameter) - #if param.id == "body_replacement": - - #print("POST data value: %s" % value) + #self.logger.info("POST data value: %s" % value) params[parameter["name"]] = value multi_parameters[parameter["name"]] = value # This code handles files. try: if parameter["schema"]["type"] == "file" and len(value) > 0: - print("\n SHOULD HANDLE FILE. Get based on value %s. <--- is this a valid ID?" % parameter["value"]) + self.logger.info("\n SHOULD HANDLE FILE. Get based on value %s. <--- is this a valid ID?" % parameter["value"]) file_value = self.get_file(value) - print("FILE VALUE: %s \n" % file_value) + self.logger.info("FILE VALUE: %s \n" % file_value) params[parameter["name"]] = file_value multi_parameters[parameter["name"]] = file_value except KeyError as e: - print("SCHEMA ERROR IN FILE HANDLING: %s" % e) + self.logger.info("SCHEMA ERROR IN FILE HANDLING: %s" % e) #remove_params.append(parameter["name"]) # Fix lists here # FIXME: This doesn't really do anything anymore - print("CHECKING multi execution list!") + self.logger.info("[DEBUG] CHECKING multi execution list: %d!" % len(multi_execution_lists)) if len(multi_execution_lists) > 0: - print("\n Multi execution list has more data: %d" % len(multi_execution_lists)) + self.logger.info("\n [DEBUG] Multi execution list has more data: %d" % len(multi_execution_lists)) filteredlist = [] for listitem in multi_execution_lists: if listitem in filteredlist: @@ -2272,55 +2477,55 @@ class AppBase: #else: # filteredlist.append(listitem) - #print("New list length: %d" % len(filteredlist)) + #self.logger.info("New list length: %d" % len(filteredlist)) if len(filteredlist) > 1: - print(f"Calculating new multi-loop length with {len(filteredlist)} lists") + self.logger.info(f"Calculating new multi-loop length with {len(filteredlist)} lists") tmplength = 1 for innerlist in filteredlist: tmplength = len(innerlist)*tmplength - print("List length: %d. %d*%d" % (tmplength, len(innerlist), tmplength)) + self.logger.info("List length: %d. %d*%d" % (tmplength, len(innerlist), tmplength)) minlength = tmplength - print("New multi execution length: %d\n" % tmplength) + self.logger.info("New multi execution length: %d\n" % tmplength) # Cleaning up extra list params for subparam in remove_params: - #print(f"DELETING {subparam}") + #self.logger.info(f"DELETING {subparam}") try: del params[subparam] except: pass - #print(f"Error with subparam deletion of {subparam} in {params}") + #self.logger.info(f"Error with subparam deletion of {subparam} in {params}") try: del multi_parameters[subparam] except: - #print(f"Error with subparam deletion of {subparam} in {multi_parameters} (2)") + #self.logger.info(f"Error with subparam deletion of {subparam} in {multi_parameters} (2)") pass - #print() - #print(f"Param: {params}") - #print(f"Multiparams: {multi_parameters}") - #print() + #self.logger.info() + #self.logger.info(f"Param: {params}") + #self.logger.info(f"Multiparams: {multi_parameters}") + #self.logger.info() if not multiexecution: # Runs a single iteration here new_params = self.validate_unique_fields(params) - print(f"Returned with newparams of length {len(new_params)}") + self.logger.info(f"[DEBUG] Returned with newparams of length {len(new_params)}") if isinstance(new_params, list) and len(new_params) == 1: params = new_params[0] else: - print("[WARNING] SHOULD STOP EXECUTION BECAUSE FIELDS AREN'T UNIQUE") - action_result["status"] = "SKIPPED" - action_result["result"] = f"A non-unique value was found" - action_result["completed_at"] = int(time.time()) - self.send_result(action_result, headers, stream_path) + self.logger.info("[WARNING] SHOULD STOP EXECUTION BECAUSE FIELDS AREN'T UNIQUE") + self.action_result["status"] = "SKIPPED" + self.action_result["result"] = f"A non-unique value was found" + self.action_result["completed_at"] = int(time.time()) + self.send_result(self.action_result, headers, stream_path) return - print("[INFO] Running normal execution\n") + self.logger.info("[INFO] Running normal execution (not loop)\n") #newres = await func(**params) - #print("PARAMS: %s" % params) + #self.logger.info("PARAMS: %s" % params) #newres = "" while True: try: @@ -2328,7 +2533,8 @@ class AppBase: break except TypeError as e: newres = "" - errorstring = "%s" % e + self.logger.info(f"[DEBUG] Got exec error: {errorstring}") + errorstring = f"{e}" if "got an unexpected keyword argument" in errorstring: fieldsplit = errorstring.split("'") if len(fieldsplit) > 1: @@ -2336,32 +2542,32 @@ class AppBase: try: del params[field] - print("Removed field invalid field %s" % field) + self.logger.info("[WARNING] Removed field invalid field %s" % field) except KeyError: break else: raise e #break - print("\n[INFO] Returned from execution with types %s" % type(newres)) - #print("\n[INFO] Returned from execution with %s of types %s" % (newres, type(newres)))#, newres) + self.logger.info("\n[INFO] Returned from execution with types %s" % type(newres)) + #self.logger.info("\n[INFO] Returned from execution with %s of types %s" % (newres, type(newres)))#, newres) if isinstance(newres, tuple): - print("[INFO] Handling return as tuple") + self.logger.info("[INFO] Handling return as tuple") # Handles files. filedata = "" file_ids = [] - print("TUPLE: %s" % newres[1]) + self.logger.info("TUPLE: %s" % newres[1]) if isinstance(newres[1], list): - print("[INFO] HANDLING LIST FROM RET") + self.logger.info("[INFO] HANDLING LIST FROM RET") file_ids = self.set_files(newres[1]) elif isinstance(newres[1], object): - print("[INFO] Handling JSON from ret") + self.logger.info("[INFO] Handling JSON from ret") file_ids = self.set_files([newres[1]]) elif isinstance(newres[1], str): - print("[INFO] Handling STRING from ret") + self.logger.info("[INFO] Handling STRING from ret") file_ids = self.set_files([newres[1]]) else: - print("[INFO] NO FILES TO HANDLE") + self.logger.info("[INFO] NO FILES TO HANDLE") tmp_result = { "result": newres[0], @@ -2370,33 +2576,33 @@ class AppBase: result = json.dumps(tmp_result) elif isinstance(newres, str): - print("[INFO] Handling return as string of length %d" % len(newres)) + self.logger.info("[INFO] Handling return as string of length %d" % len(newres)) result += newres elif isinstance(newres, dict) or isinstance(newres, list): try: result += json.dumps(newres, indent=4) except json.JSONDecodeError as e: - print("Failed decoding result: %s" % e) + self.logger.info("Failed decoding result: %s" % e) try: result += str(newres) except ValueError: result += "Failed autocasting. Can't handle %s type from function. Must be string" % type(newres) - print("Can't handle type %s value from function" % (type(newres))) + self.logger.info("Can't handle type %s value from function" % (type(newres))) else: try: result += str(newres) except ValueError: result += "Failed autocasting. Can't handle %s type from function. Must be string" % type(newres) - print("Can't handle type %s value from function" % (type(newres))) + self.logger.info("Can't handle type %s value from function" % (type(newres))) - print("[INFO] POST NEWRES RESULT!")#, result) + self.logger.info("[INFO] POST NEWRES RESULT!")#, result) else: - #print("[INFO] APP_SDK DONE: Starting MULTI execution (length: %d) with values %s" % (minlength, multi_parameters)) + #self.logger.info("[INFO] APP_SDK DONE: Starting MULTI execution (length: %d) with values %s" % (minlength, multi_parameters)) # 1. Use number of executions based on the arrays being similar # 2. Find the right value from the parsed multi_params - print("[INFO] Running WITHOUT outer loop") + self.logger.info("[INFO] Running WITHOUT outer loop (looping)") json_object = False results = await self.run_recursed_items(func, multi_parameters, {}) if isinstance(results, dict) or isinstance(results, list): @@ -2404,14 +2610,14 @@ class AppBase: #for i in range(0, minlength): # # To be able to use the results as a list: - # print("1: %s" % multi_parameters) + # self.logger.info("1: %s" % multi_parameters) # #baseparams = json.loads(json.dumps(multi_parameters)) # baseparams = copy.deepcopy(multi_parameters) - # print("2: %s: %s" % (type(baseparams), baseparams)) + # self.logger.info("2: %s: %s" % (type(baseparams), baseparams)) - # print("4") - # print("Running with params (1): %s" % baseparams) + # self.logger.info("4") + # self.logger.info("Running with params (1): %s" % baseparams) # results = await self.run_recursed_items(func, baseparams, {}) # if isinstance(results, dict) or isinstance(results, list): @@ -2427,7 +2633,7 @@ class AppBase: #try: # firstlist = True # for key, value in baseparams.items(): - # print("Itemtype: %s" % type(value)) + # self.logger.info("Itemtype: %s" % type(value)) # if isinstance(value, list): # try: # newvalue = value[i] @@ -2436,16 +2642,16 @@ class AppBase: # if len(value) != minlength and len(value) > 0: # newarray = [] - # print("VALUE: ", value) + # self.logger.info("VALUE: ", value) # additiontime = minlength/len(value) - # print("Bad length for value: %d - should be %d. Additiontime: %d" % (len(value), minlength, additiontime)) + # self.logger.info("Bad length for value: %d - should be %d. Additiontime: %d" % (len(value), minlength, additiontime)) # if firstlist: - # print("Running normal list (FIRST)") + # self.logger.info("Running normal list (FIRST)") # for subvalue in value: # for number in range(int(additiontime)): # newarray.append(subvalue) # else: - # #print("Running secondary lists") + # #self.logger.info("Running secondary lists") # ## 1. Set up length of array # ## 2. Put values spread out # # FIXME: This works well, except if lists are same length @@ -2455,31 +2661,31 @@ class AppBase: # for number in range(int(additiontime)): # for subvaluerange in range(len(value)): # # newlocation = number+(additiontime*subvaluerange) - # # print("%d+(%d*%d) = %d. VAL: %s" % (number, additiontime, subvaluerange, newlocation, value[subvaluerange])) + # # self.logger.info("%d+(%d*%d) = %d. VAL: %s" % (number, additiontime, subvaluerange, newlocation, value[subvaluerange])) # # Reverse if same length? # if int(minlength/len(value)) == len(value): # tmp = int(len(value)-subvaluerange-1) - # print("NEW: %d" % tmp) + # self.logger.info("NEW: %d" % tmp) # newarray[cnt] = value[tmp] # else: # newarray[cnt] = value[subvaluerange] # cnt += 1 - # #print("Newarray =", newarray) + # #self.logger.info("Newarray =", newarray) # newvalue = newarray[i] # firstlist = False # baseparams[key] = newvalue - # print("3") + # self.logger.info("3") #except IndexError as e: - # print("IndexError: %s" % e) + # self.logger.info("IndexError: %s" % e) # baseparams[key] = "IndexError: %s" % e #except KeyError as e: - # print("KeyError: %s" % e) + # self.logger.info("KeyError: %s" % e) # baseparams[key] = "KeyError: %s" % e - #print("4") - #print("Running with params (1): %s" % baseparams) + #self.logger.info("4") + #self.logger.info("Running with params (1): %s" % baseparams) #results = await self.run_recursed_items(func, baseparams, {}) #if isinstance(results, dict) or isinstance(results, list): @@ -2488,7 +2694,7 @@ class AppBase: # Check the structure here. If "isloop", try to recurse? # ret, is_loop = recurse_json(innervalue, parsersplit[outercnt+1:]) #ret = await func(**baseparams) - #print("Return from execution: %s" % ret) + #self.logger.info("Return from execution: %s" % ret) #if ret == None: # results.append("") # json_object = False @@ -2502,22 +2708,22 @@ class AppBase: # results.append(json.loads(ret)) # json_object = True # except json.decoder.JSONDecodeError as e: - # #print("Json: %s" % e) + # #self.logger.info("Json: %s" % e) # results.append(ret) - #print("Inner ret parsed: %s" % ret) + #self.logger.info("Inner ret parsed: %s" % ret) # Dump the result as a string of a list - #print("RESULTS: %s" % results) + #self.logger.info("RESULTS: %s" % results) if isinstance(results, list) or isinstance(results, dict): - print("JSON OBJECT? ", json_object) + self.logger.info("JSON OBJECT? ", json_object) # This part is weird lol if json_object: try: result = json.dumps(results) except json.JSONDecodeError as e: - print(f"Failed to decode: {e}") + self.logger.info(f"Failed to decode: {e}") result = results else: result = "[" @@ -2527,7 +2733,7 @@ class AppBase: result += item except json.decoder.JSONDecodeError as e: # Common nested issue which puts " around everything - print("Decodingerror: %s" % e) + self.logger.info("Decodingerror: %s" % e) try: tmpitem = item.replace("\\\"", "\"", -1) json.loads(tmpitem) @@ -2541,36 +2747,50 @@ class AppBase: result = result[:-2] result += "]" else: - print("Normal result - no list?") + self.logger.info("Normal result - no list?") result = results - action_result["status"] = "SUCCESS" - action_result["result"] = str(result) - if action_result["result"] == "": - action_result["result"] = result + self.action_result["status"] = "SUCCESS" + self.action_result["result"] = str(result) + if self.action_result["result"] == "": + self.action_result["result"] = result - self.logger.debug(f"Executed {action['label']}-{action['id']}")#with result: {result}") + self.logger.debug(f"[DEBUG] Executed {action['label']}-{action['id']}")#with result: {result}") #self.logger.debug(f"Data: %s" % action_result) except TypeError as e: - print("TypeError issue: %s" % e) - action_result["status"] = "FAILURE" - action_result["result"] = "TypeError: %s" % str(e) + self.logger.info("TypeError issue: %s" % e) + self.action_result["status"] = "FAILURE" + self.action_result["result"] = "TypeError: %s" % str(e) else: - print("Function %s doesn't exist?" % action["name"]) - self.logger.error(f"App {self.__class__.__name__}.{action['name']} is not callable") - action_result["status"] = "FAILURE" - action_result["result"] = "Function %s is not callable." % actionname + self.logger.info("[DEBUG] Function %s doesn't exist?" % action["name"]) + self.logger.error(f"[ERROR] App {self.__class__.__name__}.{action['name']} is not callable") + self.action_result["status"] = "FAILURE" + self.action_result["result"] = "Function %s is not callable." % actionname + + # https://ptb.discord.com/channels/747075026288902237/882017498550112286/882043773138382890 + except (requests.exceptions.RequestException, TimeoutError) as e: + self.logger.info(f"Failed to execute request: {e}") + self.logger.exception(f"Failed to execute {e}-{action['id']}") + self.action_result["status"] = "SUCCESS" + try: + self.action_result["result"] = json.dumps({ + "success": False, + "reason": f"Request error - failing silently. Details in detail section", + "details": f"{e}", + }) + except json.decoder.JSONDecodeError as e: + self.action_result["result"] = f"Request error: {e}" except Exception as e: - print(f"Failed to execute: {e}") + self.logger.info(f"Failed to execute: {e}") self.logger.exception(f"Failed to execute {e}-{action['id']}") - action_result["status"] = "FAILURE" - action_result["result"] = f"General exception: {e}" + self.action_result["status"] = "FAILURE" + self.action_result["result"] = f"General exception: {e}" - action_result["completed_at"] = int(time.time()) + self.action_result["completed_at"] = int(time.time()) # Send the result :) - self.send_result(action_result, headers, stream_path) + self.send_result(self.action_result, headers, stream_path) return @classmethod @@ -2579,15 +2799,16 @@ class AppBase: logger = logging.getLogger(f"{cls.__name__}") logger.setLevel(logging.DEBUG) - #print("Started execution: %s!!" % cls) - #print("Action: %s" % action) + #self.logger.info("Started execution: %s!!" % cls) + #self.logger.info("Action: %s" % action) #if isinstance(cls, object): # self.action = cls app = cls(redis=None, logger=logger, console_logger=logger) if isinstance(action, str): - print("Normal execution. Action is a string.") + print("[DEBUG] Normal execution. Action is a string.") elif isinstance(action, object): + print("[DEBUG] OBJECT execution. Action is NOT a string.") app.action = action try: @@ -2606,6 +2827,6 @@ class AppBase: except: pass else: - print("ACTION TYPE (unhandled): %s" % type(action)) + self.logger.info("ACTION TYPE (unhandled): %s" % type(action)) await app.execute_action(app.action) diff --git a/backend/app_sdk/build.sh b/backend/app_sdk/build.sh index 1e52f74d..a7518354 100644 --- a/backend/app_sdk/build.sh +++ b/backend/app_sdk/build.sh @@ -3,10 +3,10 @@ ### DEFAULT NAME=shuffle-app_sdk -VERSION=0.8.104 +VERSION=0.9.23 docker rmi docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION --force -docker build . -f Dockerfile -t frikky/shuffle:app_sdk -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION +docker build . -f Dockerfile -t frikky/shuffle:app_sdk -t frikky/$NAME:$VERSION -t docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION -t ghcr.io/frikky/$NAME:$VERSION -t ghcr.io/frikky/$NAME:nightly #docker push frikky/$NAME:$VERSION #docker push docker.pkg.github.com/frikky/shuffle/$NAME:$VERSION diff --git a/backend/app_sdk/requirements.txt b/backend/app_sdk/requirements.txt index 60b59271..79cf5ea2 100644 --- a/backend/app_sdk/requirements.txt +++ b/backend/app_sdk/requirements.txt @@ -1,2 +1,4 @@ -urllib3==1.25.9 +urllib3==1.26.5 requests==2.25.1 +MarkupSafe==2.0.1 +liquidpy==0.7.1 diff --git a/backend/database/Dockerfile b/backend/database/Dockerfile deleted file mode 100644 index 2b1758f7..00000000 --- a/backend/database/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -# 2. docker run -p 8000:8000 -FROM google/cloud-sdk - -EXPOSE 8000 -CMD ["gcloud", "beta", "emulators", "datastore", "start", "--project=shuffle", "--host-port", "0.0.0.0:8000", "--data-dir=/etc/shuffle"] diff --git a/backend/database/opensearch/docker-compose.yml b/backend/database/opensearch/docker-compose.yml new file mode 100644 index 00000000..9dfae6a1 --- /dev/null +++ b/backend/database/opensearch/docker-compose.yml @@ -0,0 +1,34 @@ +version: '3' +services: + opensearch-node1: + image: opensearchproject/opensearch:latest + hostname: shuffle-database + container_name: shuffle-opensearch + environment: + - cluster.name=shuffle-cluster + - node.name=shuffle-opensearch + - discovery.seed_hosts=shuffle-opensearch + - cluster.initial_master_nodes=shuffle-opensearch + - bootstrap.memory_lock=true # along with the memlock settings below, disables swapping + - "OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m" # minimum and maximum Java heap size, recommend setting both to 50% of system RAM + - cluster.routing.allocation.disk.threshold_enabled=false + - opendistro_security.disabled=true + ulimits: + memlock: + soft: -1 + hard: -1 + nofile: + soft: 65536 # maximum number of open files for the OpenSearch user, set to at least 65536 on modern systems + hard: 65536 + volumes: + - ~/git/shuffle/shuffle-database:/usr/share/opensearch/data + ports: + - 9200:9200 + networks: + - opensearch-net + +volumes: + opensearch-data1: + +networks: + opensearch-net: diff --git a/backend/go-app/docker.go b/backend/go-app/docker.go index a4101b61..62c5878b 100644 --- a/backend/go-app/docker.go +++ b/backend/go-app/docker.go @@ -2,7 +2,7 @@ package main // Docker import ( - "github.com/frikky/shuffle-shared" + "github.com/shuffle/shuffle-shared" "archive/tar" //"bufio" @@ -220,19 +220,18 @@ func buildImageMemory(fs billy.Filesystem, tags []string, dockerfileFolder strin // docker build --build-arg http_proxy=http://my.proxy.url // Attempt at setting name according to #359: https://github.com/frikky/Shuffle/issues/359 labels := map[string]string{} - target := "" - if len(tags) > 0 { - if strings.Contains(tags[0], ":") { - version := strings.Split(tags[0], ":") - if len(version) == 2 { - target = fmt.Sprintf("shuffle-build-%s", version[1]) - tags = append(tags, target) - labels["name"] = target - } - } - } + //target := "" + //if len(tags) > 0 { + // if strings.Contains(tags[0], ":") { + // version := strings.Split(tags[0], ":") + // if len(version) == 2 { + // target = fmt.Sprintf("shuffle-build-%s", version[1]) + // tags = append(tags, target) + // labels["name"] = target + // } + // } + //} - _ = labels buildOptions := types.ImageBuildOptions{ Remove: true, Tags: tags, @@ -261,47 +260,49 @@ func buildImageMemory(fs billy.Filesystem, tags []string, dockerfileFolder strin //log.Printf("RESPONSE: %#v", imageBuildResponse) //log.Printf("Response: %#v", imageBuildResponse.Body) - //log.Printf("IMAGERESPONSE: %#v", imageBuildResponse.Body) + log.Printf("[DEBUG] IMAGERESPONSE: %#v", imageBuildResponse.Body) - defer imageBuildResponse.Body.Close() - buildBuf := new(strings.Builder) - _, newerr := io.Copy(buildBuf, imageBuildResponse.Body) - if newerr != nil { - log.Printf("Failed reading Docker build STDOUT: %s", newerr) - } else { - log.Printf("STRING: %s", buildBuf.String()) - if strings.Contains(buildBuf.String(), "errorDetail") { - log.Printf("[ERROR] Docker build:\n%s\nERROR ABOVE: Trying to pull tags from: %s", buildBuf.String(), strings.Join(tags, "\n")) + if imageBuildResponse.Body != nil { + defer imageBuildResponse.Body.Close() + buildBuf := new(strings.Builder) + _, newerr := io.Copy(buildBuf, imageBuildResponse.Body) + if newerr != nil { + log.Printf("[WARNING] Failed reading Docker build STDOUT: %s", newerr) + } else { + log.Printf("[INFO] STRING: %s", buildBuf.String()) + if strings.Contains(buildBuf.String(), "errorDetail") { + log.Printf("[ERROR] Docker build:\n%s\nERROR ABOVE: Trying to pull tags from: %s", buildBuf.String(), strings.Join(tags, "\n")) - // Handles pulling of the same image if applicable - // This fixes some issues with older versions of Docker which can't build - // on their own ( <17.05 ) - pullOptions := types.ImagePullOptions{} - downloaded := false - for _, image := range tags { - // Is this ok? Not sure. Tags shouldn't be controlled here prolly. - image = strings.ToLower(image) + // Handles pulling of the same image if applicable + // This fixes some issues with older versions of Docker which can't build + // on their own ( <17.05 ) + pullOptions := types.ImagePullOptions{} + downloaded := false + for _, image := range tags { + // Is this ok? Not sure. Tags shouldn't be controlled here prolly. + image = strings.ToLower(image) - newImage := fmt.Sprintf("%s/%s", registryName, image) - log.Printf("[INFO] Pulling image %s", newImage) - reader, err := client.ImagePull(ctx, newImage, pullOptions) - if err != nil { - log.Printf("[ERROR] Failed getting image %s: %s", newImage, err) - continue + newImage := fmt.Sprintf("%s/%s", registryName, image) + log.Printf("[INFO] Pulling image %s", newImage) + reader, err := client.ImagePull(ctx, newImage, pullOptions) + if err != nil { + log.Printf("[ERROR] Failed getting image %s: %s", newImage, err) + continue + } + + // Attempt to retag the image to not contain registry... + + //newBuf := buildBuf + downloaded = true + io.Copy(os.Stdout, reader) + log.Printf("[INFO] Successfully downloaded and built %s", newImage) } - // Attempt to retag the image to not contain registry... - - //newBuf := buildBuf - downloaded = true - io.Copy(os.Stdout, reader) - log.Printf("[INFO] Successfully downloaded and built %s", newImage) + if !downloaded { + return errors.New(fmt.Sprintf("Failed to build / download images %s", strings.Join(tags, ","))) + } + //baseDockerName } - - if !downloaded { - return errors.New(fmt.Sprintf("Failed to build / download images %s", strings.Join(tags, ","))) - } - //baseDockerName } } @@ -777,7 +778,7 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) { newClient, err := newdockerclient.NewClientFromEnv() if err != nil { - log.Printf("[WARNING] Failed setting up docker env: %s", newClient) + log.Printf("[ERROR] Failed setting up docker env: %#v", newClient) resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "Couldn't make docker client"}`))) return @@ -791,7 +792,7 @@ func getDockerImage(resp http.ResponseWriter, request *http.Request) { } if err := newClient.ExportImage(opts); err != nil { - log.Printf("[WARNING] FAILED to save image to file: %s", err) + log.Printf("[ERROR] FAILED to save image to file: %s", err) resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "message": "Couldn't export image"}`))) return diff --git a/backend/go-app/go.mod b/backend/go-app/go.mod index fcc25a96..16f22e0f 100644 --- a/backend/go-app/go.mod +++ b/backend/go-app/go.mod @@ -2,7 +2,7 @@ module shuffle go 1.13 -replace github.com/frikky/shuffle-shared => ../../../../git/shuffle-shared +replace github.com/shuffle/shuffle-shared => ../../../../git/shuffle-shared //replace github.com/frikky/kin-openapi => ../../../../git/kin-openapi @@ -12,6 +12,7 @@ require ( cloud.google.com/go/pubsub v1.3.1 cloud.google.com/go/storage v1.12.0 github.com/Masterminds/semver v1.5.0 // indirect + github.com/RobotsAndPencils/go-saml v0.0.0-20170520135329-fb13cb52a46b // indirect github.com/algolia/algoliasearch-client-go/v3 v3.18.1 // indirect github.com/basgys/goxml2json v1.1.0 github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect @@ -22,7 +23,7 @@ require ( github.com/docker/go-units v0.4.0 // indirect github.com/elastic/go-elasticsearch/v7 v7.13.1 // indirect github.com/frikky/kin-openapi v0.39.0 - github.com/frikky/shuffle-shared v0.0.69 + github.com/frikky/shuffle-shared v0.1.15 github.com/fsouza/go-dockerclient v1.7.2 github.com/ghodss/yaml v1.0.0 github.com/go-git/go-billy/v5 v5.0.0 @@ -31,8 +32,11 @@ require ( github.com/gorilla/handlers v1.4.2 // indirect github.com/gorilla/mux v1.8.0 github.com/h2non/filetype v1.0.12 + github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect + github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect github.com/patrickmn/go-cache v2.1.0+incompatible github.com/satori/go.uuid v1.2.0 + github.com/shuffle/shuffle-shared v0.1.15 go4.org v0.0.0-20201209231011-d4a079459e60 // indirect golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/oauth2 v0.0.0-20210113160501-8b1d76fa0423 diff --git a/backend/go-app/main.go b/backend/go-app/main.go index 825a20bb..2b644674 100644 --- a/backend/go-app/main.go +++ b/backend/go-app/main.go @@ -1,12 +1,13 @@ package main import ( - "github.com/frikky/shuffle-shared" + "github.com/shuffle/shuffle-shared" "bufio" "bytes" "context" "crypto/md5" + //"crypto/tls" //"crypto/x509" "encoding/hex" "encoding/json" @@ -48,6 +49,7 @@ import ( "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/storage/memory" + //githttp "gopkg.in/src-d/go-git.v4/plumbing/transport/http" // Random xj "github.com/basgys/goxml2json" @@ -57,7 +59,7 @@ import ( "gopkg.in/yaml.v3" // PROXY overrides - // "gopkg.in/src-d/go-git.v4/plumbing/transport/client" + //"gopkg.in/src-d/go-git.v4/plumbing/transport/client" // githttp "gopkg.in/src-d/go-git.v4/plumbing/transport/http" // Web @@ -578,23 +580,6 @@ func redirect(w http.ResponseWriter, req *http.Request) { http.StatusTemporaryRedirect) } -func parseLoginParameters(resp http.ResponseWriter, request *http.Request) (loginStruct, error) { - - body, err := ioutil.ReadAll(request.Body) - if err != nil { - return loginStruct{}, err - } - - var t loginStruct - - err = json.Unmarshal(body, &t) - if err != nil { - return loginStruct{}, err - } - - return t, nil -} - // No more emails :) func checkUsername(Username string) error { // Stupid first check of email loool @@ -729,6 +714,7 @@ func handleRegister(resp http.ResponseWriter, request *http.Request) { // Only admin can CREATE users, but if there are no users, anyone can make (first) ctx := context.Background() users, countErr := shuffle.GetAllUsers(ctx) + count := len(users) user, err := shuffle.HandleApiAuthentication(resp, request) if err != nil { @@ -739,16 +725,19 @@ func handleRegister(resp http.ResponseWriter, request *http.Request) { } } + apikey := "" if count != 0 { if user.Role != "admin" { resp.WriteHeader(401) resp.Write([]byte(`{"success": false, "reason": "Can't register without being admin (2)"}`)) return } + } else { + apikey = uuid.NewV4().String() } // Gets a struct of Username, password - data, err := parseLoginParameters(resp, request) + data, err := shuffle.ParseLoginParameters(resp, request) if err != nil { log.Printf("Invalid params: %s", err) resp.WriteHeader(401) @@ -808,7 +797,7 @@ func handleRegister(resp http.ResponseWriter, request *http.Request) { err = shuffle.SetEnvironment(ctx, &item) if err != nil { - log.Printf("[WARNING] Failed setting up new environment for new org: %s") + log.Printf("[WARNING] Failed setting up new environment for new org: %s", err) } currentOrg = shuffle.OrgMini{ @@ -819,7 +808,7 @@ func handleRegister(resp http.ResponseWriter, request *http.Request) { } } - err = createNewUser(data.Username, data.Password, role, "", currentOrg) + err = createNewUser(data.Username, data.Password, role, apikey, currentOrg) if err != nil { log.Printf("[WARNING] Failed registering user: %s", err) resp.WriteHeader(401) @@ -828,7 +817,7 @@ func handleRegister(resp http.ResponseWriter, request *http.Request) { } resp.WriteHeader(200) - resp.Write([]byte(`{"success": true}`)) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "apikey": "%s"}`, apikey))) log.Printf("[INFO] %s Successfully registered.", data.Username) } @@ -854,6 +843,7 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { userInfo, err := shuffle.HandleApiAuthentication(resp, request) if err != nil { log.Printf("[WARNING] Api authentication failed in handleInfo: %s", err) + resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return @@ -976,38 +966,66 @@ func handleInfo(resp http.ResponseWriter, request *http.Request) { } err = shuffle.SetUser(ctx, &userInfo, true) if err != nil { - log.Printf("Error patching User for activeOrg: %s", err) + log.Printf("[INFO] Error patching User for activeOrg: %s", err) } } } - // FIXME: Remove this dependency by updating users' orgs when org itself is updated org, err := shuffle.GetOrg(ctx, userInfo.ActiveOrg.Id) if err == nil { userInfo.ActiveOrg = shuffle.OrgMini{ - Id: org.Id, - Name: org.Name, + Id: org.Id, + Name: org.Name, + CreatorOrg: org.CreatorOrg, + Role: userInfo.ActiveOrg.Role, + Image: org.Image, } - - userInfo.ActiveOrg.Users = []shuffle.UserMini{} } userInfo.ActiveOrg.Users = []shuffle.UserMini{} - currentOrg, err := json.Marshal(userInfo.ActiveOrg) - if err != nil { - currentOrg = []byte("{}") + userOrgs := []shuffle.OrgMini{} + for _, item := range userInfo.Orgs { + if item == userInfo.ActiveOrg.Id { + userOrgs = append(userOrgs, userInfo.ActiveOrg) + continue + } + + org, err := shuffle.GetOrg(ctx, item) + if err == nil { + userOrgs = append(userOrgs, shuffle.OrgMini{ + Id: org.Id, + Name: org.Name, + CreatorOrg: org.CreatorOrg, + Image: org.Image, + }) + } else { + log.Printf("[WARNING] Failed to get org %s for user %s", item, userInfo.Username) + } } - returnData := fmt.Sprintf(`{ - "success": true, - "username": "%s", - "admin": %s, - "tutorials": [], - "id": "%s", - "orgs": [%s], - "active_org": %s, - "cookies": [{"key": "session_token", "value": "%s", "expiration": %d}] -}`, userInfo.Username, parsedAdmin, userInfo.Id, currentOrg, currentOrg, userInfo.Session, expiration.Unix()) + returnValue := shuffle.HandleInfo{ + Success: true, + Username: userInfo.Username, + Admin: parsedAdmin, + Id: userInfo.Id, + Orgs: userOrgs, + ActiveOrg: userInfo.ActiveOrg, + Cookies: []shuffle.SessionCookie{ + shuffle.SessionCookie{ + Key: "session_token", + Value: userInfo.Session, + Expiration: expiration.Unix(), + }, + }, + } + + returnData, err := json.Marshal(returnValue) + if err != nil { + log.Printf("[WARNING] Failed marshalling info in handleinfo: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } resp.WriteHeader(200) resp.Write([]byte(returnData)) @@ -1147,8 +1165,10 @@ func checkAdminLogin(resp http.ResponseWriter, request *http.Request) { return } + //ssoUrl = org.SSOConfig.SOSOEntrypoint + redirectUri := shuffle.SSOUrl resp.WriteHeader(200) - resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "redirect"}`))) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "redirect", "sso_url": "%s"}`, redirectUri))) } func handleLogin(resp http.ResponseWriter, request *http.Request) { @@ -1158,7 +1178,7 @@ func handleLogin(resp http.ResponseWriter, request *http.Request) { } // Gets a struct of Username, password - data, err := parseLoginParameters(resp, request) + data, err := shuffle.ParseLoginParameters(resp, request) if err != nil { resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) @@ -1935,6 +1955,10 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { // 1. Get callback data // 2. Load the configuration // 3. Execute the workflow + cors := shuffle.HandleCors(resp, request) + if cors { + return + } path := strings.Split(request.URL.String(), "/") if len(path) < 4 { @@ -1949,8 +1973,10 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { location := strings.Split(request.URL.String(), "/") var hookId string + var queries string if location[1] == "api" { if len(location) <= 4 { + log.Printf("[INFO] Couldn't handle location. Too short in webhook: %d", len(location)) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return @@ -1959,10 +1985,20 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { hookId = location[4] } + if strings.Contains(hookId, "?") { + splitter := strings.Split(hookId, "?") + hookId = splitter[0] + + if len(splitter) > 1 { + queries = splitter[1] + } + } + // ID: webhook_ if len(hookId) != 44 { + log.Printf("[INFO] Couldn't handle hookId. Too short in webhook: %d", len(hookId)) resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "message": "ID not valid"}`)) + resp.Write([]byte(`{"success": false, "reason": "Hook ID not valid"}`)) return } @@ -1986,19 +2022,19 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { if hook.Status == "stopped" { log.Printf("[WARNING] Not running %s because hook status is stopped", hook.Id) resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "The webhook isn't running. Click start to start it"}`))) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "The webhook isn't running. Is it running?"}`))) return } if len(hook.Workflows) == 0 { - log.Printf("Not running because hook isn't connected to any workflows") + log.Printf("[DEBUG] Not running because hook isn't connected to any workflows") resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "No workflows are defined"}`))) return } if hook.Environment == "cloud" { - log.Printf("This should trigger in the cloud. Duplicate action allowed onprem.") + log.Printf("[DEBUG] This should trigger in the cloud. Duplicate action allowed onprem.") } // Check auth @@ -2014,12 +2050,16 @@ func handleWebhookCallback(resp http.ResponseWriter, request *http.Request) { body, err := ioutil.ReadAll(request.Body) if err != nil { - log.Printf("Body data error: %s", err) + log.Printf("[DEBUG] Body data error: %s", err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return } + if len(queries) > 0 && len(body) == 0 { + body = []byte(queries) + } + //log.Printf("BODY: %s", parsedBody) // This is a specific fix for MSteams and may fix other things as well @@ -2567,7 +2607,6 @@ func findValidScheduleAppFolders(rootAppFolder string) ([]string, error) { appFiles, err := ioutil.ReadDir(appFolderLocation) if err != nil { // Invalid app folder (deleted within a few MS lol) - log.Printf("%s", err) invalidRootFolders = append(invalidRootFolders, rootfile.Name()) continue } @@ -2691,121 +2730,6 @@ func validateAppYaml(fileLocation string) error { return nil } -func handleSendalert(resp http.ResponseWriter, request *http.Request) { - user, err := shuffle.HandleApiAuthentication(resp, request) - if err != nil { - log.Printf("[WARNING] Api authentication failed in sendalert: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - if user.Role != "mail" && user.Role != "admin" { - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": "You don't have access to send mail"}`)) - return - } - - // ReferenceExecution and below are for execution continuations when user inputs arrive - type mailcheck struct { - Targets []string `json:"targets"` - Body string `json:"body"` - Subject string `json:"subject"` - Type string `json:"type"` - SenderCompany string `json:"sender_company"` - ReferenceExecution string `json:"reference_execution"` - WorkflowId string `json:"workflow_id"` - ExecutionType string `json:"execution_type"` - Start string `json:"start"` - } - - body, err := ioutil.ReadAll(request.Body) - if err != nil { - log.Printf("Body data error on mail: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - var mailbody mailcheck - err = json.Unmarshal(body, &mailbody) - if err != nil { - log.Printf("Unmarshal error on mail: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - ctx := context.Background() - confirmMessage := ` -You have a new alert from shuffler.io! - -%s - -Please contact us at shuffler.io or frikky@shuffler.io if there is an issue with this message.` - - parsedBody := fmt.Sprintf(confirmMessage, mailbody.Body) - - // FIXME - Make a continuation email here - might need more info from worker - // making the request, e.g. what the next start-node is and execution_id for - // how to make the links - if mailbody.Type == "User input" { - authkey := uuid.NewV4().String() - - log.Printf("Should handle differentiator for user input in email!") - log.Printf("%#v", mailbody) - - url := "https://shuffler.io" - //url := "http://localhost:5001" - continueUrl := fmt.Sprintf("%s/api/v1/workflows/%s/execute?authorization=%s&start=%s&reference_execution=%s&answer=true", url, mailbody.WorkflowId, authkey, mailbody.Start, mailbody.ReferenceExecution) - stopUrl := fmt.Sprintf("%s/api/v1/workflows/%s/execute?authorization=%s&start=%s&reference_execution=%s&answer=false", url, mailbody.WorkflowId, authkey, mailbody.Start, mailbody.ReferenceExecution) - - //item := &memcache.Item{ - // Key: authkey, - // Value: []byte(fmt.Sprintf(`{"role": "workflow_%s"}`, mailbody.WorkflowId)), - // Expiration: time.Minute * 1200, - //} - - //if err := memcache.Add(ctx, item); err == memcache.ErrNotStored { - // if err := memcache.Set(ctx, item); err != nil { - // log.Printf("Error setting new user item: %v", err) - // } - //} else if err != nil { - // log.Printf("error adding item: %v", err) - //} else { - // log.Printf("Set cache for %s", item.Key) - //} - - parsedBody = fmt.Sprintf(` -Action required! - -%s - -If this is TRUE click this: %s - -IF THIS IS FALSE, click this: %s - -Please contact us at shuffler.io or frikky@shuffler.io if there is an issue with this message. -`, mailbody.Body, continueUrl, stopUrl) - - } - - msg := &mail.Message{ - Sender: "Shuffle ", - To: mailbody.Targets, - Subject: fmt.Sprintf("Shuffle - %s - %s", mailbody.Type, mailbody.Subject), - Body: parsedBody, - } - - log.Println(msg.Body) - if err := mail.Send(ctx, msg); err != nil { - log.Printf("Couldn't send email: %v", err) - } - - resp.WriteHeader(200) - resp.Write([]byte(`{"success": true}`)) -} - func setBadMemcache(ctx context.Context, path string) { // Add to cache if it doesn't exist //item := &memcache.Item{ @@ -3133,7 +3057,6 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User) { swagger, err := swaggerLoader.LoadSwaggerFromData(body) if err != nil { log.Printf("[ERROR] Swagger validation error: %s", err) - //log.Printf("%s", string(body)) resp.WriteHeader(500) resp.Write([]byte(`{"success": false, "reason": "Failed verifying openapi"}`)) return @@ -3161,7 +3084,7 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User) { //log.Printf("Should generate yaml") swagger, api, pythonfunctions, err := shuffle.GenerateYaml(swagger, newmd5) if err != nil { - log.Printf("Failed building and generating yaml: %s", err) + log.Printf("[WARNING] Failed building and generating yaml (buildapp): %s", err) resp.WriteHeader(500) resp.Write([]byte(`{"success": false, "reason": "Failed building and parsing yaml"}`)) return @@ -3331,9 +3254,12 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User) { log.Printf("[ERROR] Failed saving app %s to database: %s", newmd5, err) resp.WriteHeader(500) resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "%"}`, err))) + return } shuffle.SetOpenApiDatastore(ctx, api.ID, parsed) + } else { + //log.Printf(" } // Backup every single one @@ -3358,6 +3284,7 @@ func buildSwaggerApp(resp http.ResponseWriter, body []byte, user shuffle.User) { shuffle.DeleteCache(ctx, cacheKey) shuffle.DeleteCache(ctx, fmt.Sprintf("apps_%s", user.Id)) + log.Printf("[DEBUG] Successfully built app %s (%s)", api.Name, api.ID) if len(user.Id) > 0 { resp.WriteHeader(200) resp.Write([]byte(fmt.Sprintf(`{"success": true, "id": "%s"}`, api.ID))) @@ -3468,7 +3395,7 @@ func handleAppHotload(ctx context.Context, location string, forceUpdate bool) er } //log.Printf("Reading app folder: %#v", dir) - _, _, err = iterateAppGithubFolders(fs, dir, "", "", forceUpdate) + _, _, err = IterateAppGithubFolders(ctx, fs, dir, "", "", forceUpdate) if err != nil { log.Printf("[WARNING] Githubfolders error: %s", err) return err @@ -3552,7 +3479,7 @@ func handleCloudJob(job shuffle.CloudSyncJob) error { if job.Action == "execute" { // FIXME: Get the email ctx := context.Background() - maildata := shuffle.MailData{} + maildata := shuffle.MailDataOutlook{} err := json.Unmarshal([]byte(job.ThirdItem), &maildata) if err != nil { log.Printf("Maildata unmarshal error: %s", err) @@ -3568,13 +3495,13 @@ func handleCloudJob(job shuffle.CloudSyncJob) error { redirectDomain := "localhost:5001" redirectUrl := fmt.Sprintf("http://%s/api/v1/triggers/outlook/register", redirectDomain) - outlookClient, _, err := getOutlookClient(ctx, "", hook.OauthToken, redirectUrl) + outlookClient, _, err := shuffle.GetOutlookClient(ctx, "", hook.OauthToken, redirectUrl) if err != nil { log.Printf("Oauth client failure - triggerauth: %s", err) return err } - emails, err := getOutlookEmail(outlookClient, maildata) + emails, err := shuffle.GetOutlookEmail(outlookClient, maildata) //log.Printf("EMAILS: %d", len(emails)) //log.Printf("INSIDE GET OUTLOOK EMAIL!: %#v, %s", emails, err) @@ -3841,6 +3768,15 @@ func runInitCloudSetup() { func runInitEs(ctx context.Context) { log.Printf("[DEBUG] Starting INIT setup (ES)") + httpProxy := os.Getenv("HTTP_PROXY") + if len(httpProxy) > 0 { + log.Printf("Running with HTTP proxy %s (env: HTTP_PROXY)", httpProxy) + } + httpsProxy := os.Getenv("HTTPS_PROXY") + if len(httpsProxy) > 0 { + log.Printf("Running with HTTPS proxy %s (env: HTTPS_PROXY)", httpsProxy) + } + defaultEnv := os.Getenv("ORG_ID") if len(defaultEnv) == 0 { defaultEnv = "Shuffle" @@ -3941,17 +3877,11 @@ func runInitEs(ctx context.Context) { log.Printf("[WARNING] Failed getting schedules during service init: %s", err) } else { log.Printf("[INFO] Setting up %d schedule(s)", len(schedules)) - url := &url.URL{} - for _, schedule := range schedules { - if schedule.Environment == "cloud" { - log.Printf("Skipping cloud schedule") - continue - } - //log.Printf("Schedule: %#v", schedule) - job := func() { - //log.Printf("[INFO] Running schedule %s with interval %d.", schedule.Id, schedule.Seconds) - //log.Printf("ARG: %s", schedule.WrappedArgument) + url := &url.URL{} + job := func(schedule shuffle.ScheduleOld) func() { + return func() { + log.Printf("[INFO] Running schedule %s with interval %d.", schedule.Id, schedule.Seconds) request := &http.Request{ URL: url, @@ -3964,9 +3894,17 @@ func runInitEs(ctx context.Context) { log.Printf("[WARNING] Failed to execute %s: %s", schedule.WorkflowId, err) } } + } + for _, schedule := range schedules { + if schedule.Environment == "cloud" { + log.Printf("Skipping cloud schedule") + continue + } + + //log.Printf("Schedule: %#v", schedule) //log.Printf("Schedule time: every %d seconds", schedule.Seconds) - jobret, err := newscheduler.Every(schedule.Seconds).Seconds().NotImmediately().Run(job) + jobret, err := newscheduler.Every(schedule.Seconds).Seconds().NotImmediately().Run(job(schedule)) if err != nil { log.Printf("Failed to schedule workflow: %s", err) } @@ -4044,7 +3982,7 @@ func runInitEs(ctx context.Context) { for _, org := range activeOrgs { if !org.CloudSync { - log.Printf("[WARNING] Skipping org %s because sync isn't set (1).", org.Id) + log.Printf("[WARNING] Skipping org syncCheck for %s because sync isn't set (1).", org.Id) continue } @@ -4083,6 +4021,17 @@ func runInitEs(ctx context.Context) { // FIXME: Isn't this a little backwards? workflowapps, err := shuffle.GetAllWorkflowApps(ctx, 1000) log.Printf("[INFO] Getting and validating workflowapps. Got %d with err %#v", len(workflowapps), err) + + // accept any certificate (might be useful for testing) + //customGitClient := &http.Client{ + // Transport: &http.Transport{ + // TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + // }, + // Timeout: 15 * time.Second, + //} + //client.InstallProtocol("http", githttp.NewClient(customGitClient)) + //client.InstallProtocol("https", githttp.NewClient(customGitClient)) + if err != nil && len(workflowapps) == 0 { log.Printf("[WARNING] Failed getting apps (runInit): %s", err) } else if err == nil { @@ -4108,6 +4057,7 @@ func runInitEs(ctx context.Context) { Password: password, } } + branch := os.Getenv("SHUFFLE_DOWNLOAD_AUTH_BRANCH") if len(branch) > 0 && branch != "master" && branch != "main" { cloneOptions.ReferenceName = plumbing.ReferenceName(branch) @@ -4118,18 +4068,18 @@ func runInitEs(ctx context.Context) { r, err := git.Clone(storer, fs, cloneOptions) if err != nil { - log.Printf("Failed loading repo into memory (init): %s", err) + log.Printf("[WARNING] Failed loading repo into memory (init): %s", err) } dir, err := fs.ReadDir("") if err != nil { - log.Printf("Failed reading folder: %s", err) + log.Printf("[WARNING] Failed reading folder (init): %s", err) } _ = r //iterateAppGithubFolders(fs, dir, "", "testing") // FIXME: Get all the apps? - _, _, err = iterateAppGithubFolders(fs, dir, "", "", forceUpdate) + _, _, err = IterateAppGithubFolders(ctx, fs, dir, "", "", forceUpdate) if err != nil { log.Printf("[WARNING] Error from app load in init: %s", err) } @@ -4154,7 +4104,7 @@ func runInitEs(ctx context.Context) { } _, err = git.Clone(storer, fs, cloneOptions) if err != nil { - log.Printf("Failed loading repo %s into memory: %s", apis, err) + log.Printf("[WARNING] Failed loading repo %s into memory: %s", apis, err) } else { log.Printf("[INFO] Finished git clone. Looking for updates to the repo.") dir, err := fs.ReadDir("") @@ -4784,7 +4734,7 @@ func runInit(ctx context.Context) { //iterateAppGithubFolders(fs, dir, "", "testing") // FIXME: Get all the apps? - _, _, err = iterateAppGithubFolders(fs, dir, "", "", forceUpdate) + _, _, err = IterateAppGithubFolders(ctx, fs, dir, "", "", forceUpdate) if err != nil { log.Printf("[WARNING] Error from app load in init: %s", err) } @@ -4933,6 +4883,7 @@ func handleStopCloudSync(syncUrl string, org shuffle.Org) (*shuffle.Org, error) return &org, err } + // FIXME: If it says bad API-key, stop cloud sync for the Org if newresp.StatusCode != 200 { return &org, errors.New(fmt.Sprintf("Got status code %d when disabling org remotely. Expected 200. Contact support.", newresp.StatusCode)) } @@ -5210,7 +5161,7 @@ func handleCloudSetup(resp http.ResponseWriter, request *http.Request) { // 2. If cloud env found, enable it (un-archive) // 3. If it doesn't create it environments, err := shuffle.GetEnvironments(ctx, org.Id) - log.Printf("GETTING ENVS: %#s", environments) + log.Printf("GETTING ENVS: %#v", environments) if err == nil { // Don't disable, this will be deleted entirely @@ -5304,8 +5255,8 @@ func migrateDatabase(resp http.ResponseWriter, request *http.Request) { } ctx := context.Background() - es := shuffle.GetEsConfig() - _, err := shuffle.RunInit(*dbclient, *es, storage.Client{}, gceProject, "onprem", false, "") + //es := shuffle.GetEsConfig() + _, err := shuffle.RunInit(*dbclient, storage.Client{}, gceProject, "onprem", false, "") if err != nil { log.Printf("[WARNING] Failed to start migration because of init issues: %s", err) resp.WriteHeader(401) @@ -5387,7 +5338,7 @@ func migrateDatabase(resp http.ResponseWriter, request *http.Request) { envSuccess := 0 hookSuccess := 0 scheduleSuccess := 0 - _, err = shuffle.RunInit(*dbclient, *es, storage.Client{}, gceProject, "onprem", false, "elasticsearch") + _, err = shuffle.RunInit(*dbclient, storage.Client{}, gceProject, "onprem", false, "elasticsearch") for _, item := range orgs { err = shuffle.SetOrg(ctx, item, item.Id) @@ -5569,7 +5520,7 @@ func makeWorkflowPublic(resp http.ResponseWriter, request *http.Request) { if workflow.OrgId == user.ActiveOrg.Id && user.Role == "admin" { log.Printf("[AUDIT] User %s is accessing workflow %s as admin (public)", user.Username, workflow.ID) } else { - log.Printf("[WARNING] Wrong user (%s) for workflow %s (public)", user.Username, workflow.ID) + log.Printf("[AUDIT] Wrong user (%s) for workflow %s (public)", user.Username, workflow.ID) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return @@ -5668,14 +5619,14 @@ func initHandlers() { log.Fatalf("[DEBUG] Database client error during init: %s", err) } - es := shuffle.GetEsConfig() + //es := shuffle.GetEsConfig() elasticConfig := "elasticsearch" if strings.ToLower(os.Getenv("SHUFFLE_ELASTIC")) == "false" { elasticConfig = "" } for { - _, err = shuffle.RunInit(*dbclient, *es, storage.Client{}, gceProject, "onprem", true, elasticConfig) + _, err = shuffle.RunInit(*dbclient, storage.Client{}, gceProject, "onprem", true, elasticConfig) if err != nil { log.Printf("[ERROR] Error in initial database connection. Retrying in 5 seconds. %s", err) time.Sleep(5 * time.Second) @@ -5746,8 +5697,8 @@ func initHandlers() { r.HandleFunc("/api/v1/apps/{appId}", shuffle.DeleteWorkflowApp).Methods("DELETE", "OPTIONS") r.HandleFunc("/api/v1/apps/{appId}/config", shuffle.GetWorkflowAppConfig).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/apps/run_hotload", handleAppHotloadRequest).Methods("GET", "OPTIONS") - r.HandleFunc("/api/v1/apps/get_existing", loadSpecificApps).Methods("POST", "OPTIONS") - r.HandleFunc("/api/v1/apps/download_remote", loadSpecificApps).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/apps/get_existing", LoadSpecificApps).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/apps/download_remote", LoadSpecificApps).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/apps/validate", validateAppInput).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/apps", getWorkflowApps).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/apps", setNewWorkflowApp).Methods("PUT", "OPTIONS") @@ -5775,8 +5726,6 @@ func initHandlers() { r.HandleFunc("/api/v1/workflows/download_remote", loadSpecificWorkflows).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/workflows/{key}/execute", executeWorkflow).Methods("GET", "POST", "OPTIONS") r.HandleFunc("/api/v1/workflows/{key}/schedule/{schedule}", stopSchedule).Methods("DELETE", "OPTIONS") - r.HandleFunc("/api/v1/workflows/{key}/outlook", createOutlookSub).Methods("POST", "OPTIONS") - r.HandleFunc("/api/v1/workflows/{key}/outlook/{triggerId}", handleDeleteOutlookSub).Methods("DELETE", "OPTIONS") r.HandleFunc("/api/v1/workflows/{key}", deleteWorkflow).Methods("DELETE", "OPTIONS") r.HandleFunc("/api/v1/workflows/{key}", shuffle.SaveWorkflow).Methods("PUT", "OPTIONS") r.HandleFunc("/api/v1/workflows/{key}", shuffle.GetSpecificWorkflow).Methods("GET", "OPTIONS") @@ -5794,20 +5743,35 @@ func initHandlers() { r.HandleFunc("/api/v1/get_openapi/{key}", getOpenapi).Methods("GET", "OPTIONS") // Specific triggers - r.HandleFunc("/api/v1/triggers/outlook/register", handleNewOutlookRegister).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/workflows/{key}/outlook", shuffle.HandleCreateOutlookSub).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/workflows/{key}/outlook/{triggerId}", shuffle.HandleDeleteOutlookSub).Methods("DELETE", "OPTIONS") + r.HandleFunc("/api/v1/triggers/outlook/register", shuffle.HandleNewOutlookRegister).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/triggers/outlook/getFolders", shuffle.HandleGetOutlookFolders).Methods("GET", "OPTIONS") - r.HandleFunc("/api/v1/triggers/outlook/{key}", handleGetSpecificTrigger).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/triggers/outlook/{key}", shuffle.HandleGetSpecificTrigger).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/triggers/gmail/register", shuffle.HandleNewGmailRegister).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/triggers/gmail/getFolders", shuffle.HandleGetGmailFolders).Methods("GET", "OPTIONS") + + //r.HandleFunc("/api/v1/triggers/gmail/routing", handleGmailRouting).Methods("POST", "OPTIONS") + + r.HandleFunc("/api/v1/triggers/gmail/{key}", shuffle.HandleGetSpecificTrigger).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/workflows/{key}/gmail", shuffle.HandleCreateGmailSub).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/workflows/{key}/gmail/{triggerId}", shuffle.HandleDeleteGmailSub).Methods("DELETE", "OPTIONS") + + //r.HandleFunc("/api/v1/triggers/gmail/{key}", handleGetSpecificGmailTrigger).Methods("GET", "OPTIONS") + //r.HandleFunc("/api/v1/triggers/outlook/getFolders", shuffle.HandleGetOutlookFolders).Methods("GET", "OPTIONS") + //r.HandleFunc("/api/v1/triggers/outlook/{key}", handleGetSpecificTrigger).Methods("GET", "OPTIONS") //r.HandleFunc("/api/v1/triggers/outlook/{key}/callback", handleOutlookCallback).Methods("POST", "OPTIONS") //r.HandleFunc("/api/v1/stats/{key}", handleGetSpecificStats).Methods("GET", "OPTIONS") // EVERYTHING below here is NEW for 0.8.0 (written 25.05.2021) r.HandleFunc("/api/v1/workflows/{key}/publish", makeWorkflowPublic).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/cloud/setup", handleCloudSetup).Methods("POST", "OPTIONS") - r.HandleFunc("/api/v1/orgs", shuffle.HandleGetOrgs).Methods("GET", "OPTIONS") - r.HandleFunc("/api/v1/orgs/", shuffle.HandleGetOrgs).Methods("GET", "OPTIONS") + //r.HandleFunc("/api/v1/orgs", shuffle.HandleGetOrgs).Methods("GET", "OPTIONS") + //r.HandleFunc("/api/v1/orgs/", shuffle.HandleGetOrgs).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}", shuffle.HandleGetOrg).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}", shuffle.HandleEditOrg).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/orgs/{orgId}/create_sub_org", shuffle.HandleCreateSubOrg).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/orgs/{orgId}/change", shuffle.HandleChangeUserOrg).Methods("POST", "OPTIONS") // Swaps to the org // This is a new API that validates if a key has been seen before. // Not sure what the best course of action is for it. @@ -5819,11 +5783,13 @@ func initHandlers() { // Docker orborus specific - downloads an image r.HandleFunc("/api/v1/get_docker_image", getDockerImage).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/migrate_database", migrateDatabase).Methods("POST", "OPTIONS") + r.HandleFunc("/api/v1/login_sso", shuffle.HandleSSO).Methods("POST", "OPTIONS") // Important for email, IDS etc. Create this by: // PS: For cloud, this has to use cloud storage. // https://developer.box.com/reference/get-files-id-content/ // 1. Creating the "get file" option. Make it possible to run this in the frontend. + r.HandleFunc("/api/v1/files/namespaces/{namespace}", shuffle.HandleGetFileNamespace).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/files/{fileId}/content", shuffle.HandleGetFileContent).Methods("GET", "OPTIONS") r.HandleFunc("/api/v1/files/create", shuffle.HandleCreateFile).Methods("POST", "OPTIONS") r.HandleFunc("/api/v1/files/{fileId}/upload", shuffle.HandleUploadFile).Methods("POST", "OPTIONS") @@ -5831,6 +5797,12 @@ func initHandlers() { r.HandleFunc("/api/v1/files/{fileId}", shuffle.HandleDeleteFile).Methods("DELETE", "OPTIONS") r.HandleFunc("/api/v1/files", shuffle.HandleGetFiles).Methods("GET", "OPTIONS") + // Introduced in 0.9.21 to handle notifications for e.g. failed Workflow + r.HandleFunc("/api/v1/notifications", shuffle.HandleGetNotifications).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/notifications/clear", shuffle.HandleClearNotifications).Methods("GET", "OPTIONS") + r.HandleFunc("/api/v1/notifications/{notificationId}/markasread", shuffle.HandleMarkAsRead).Methods("GET", "OPTIONS") + //r.HandleFunc("/api/v1/notifications/{notificationId}/markasread", shuffle.HandleMarkAsRead).Methods("GET", "OPTIONS") + http.Handle("/", r) } diff --git a/backend/go-app/main_test.go b/backend/go-app/main_test.go new file mode 100644 index 00000000..e9d358b6 --- /dev/null +++ b/backend/go-app/main_test.go @@ -0,0 +1,340 @@ +package main + +import ( + "github.com/shuffle/shuffle-shared" + + "bytes" + "context" + "log" + "net/http" + "net/http/httptest" + "reflect" + "runtime" + "testing" + "time" + + "cloud.google.com/go/datastore" + "cloud.google.com/go/storage" + "google.golang.org/api/option" + + "google.golang.org/grpc" +) + +type endpoint struct { + handler http.HandlerFunc + path string + method string +} + +func init() { + ctx := context.Background() + dbclient, err := datastore.NewClient(ctx, gceProject, option.WithGRPCDialOption(grpc.WithNoProxy())) + if err != nil { + log.Fatalf("[DEBUG] Database client error during init: %s", err) + } + + _, err = shuffle.RunInit(*dbclient, storage.Client{}, gceProject, "onprem", true, "elasticsearch") + log.Printf("INIT") +} + +// TestTestAuthenticationRequired tests that the handlers in the `handlers` +// variable returns 401 Unauthorized when called without credentials. +func TestAuthenticationRequired(t *testing.T) { + handlers := []endpoint{ + {handler: shuffle.HandleNewOutlookRegister, path: "/functions/outlook/register", method: "GET"}, + {handler: shuffle.HandleGetOutlookFolders, path: "/functions/outlook/getFolders", method: "GET"}, + {handler: shuffle.HandleApiGeneration, path: "/api/v1/users/generateapikey", method: "GET"}, + {handler: handleLogin, path: "/api/v1/users/login", method: "POST"}, // prob not this one + // handleRegister generates nil pointer exception. Not necessary for this anyway. + //{handler: handleRegister, path: "/api/v1/users/register", method: "POST"}, + {handler: shuffle.HandleGetUsers, path: "/api/v1/users/getusers", method: "GET"}, + {handler: handleInfo, path: "/api/v1/users/getinfo", method: "GET"}, + {handler: shuffle.HandleSettings, path: "/api/v1/users/getsettings", method: "GET"}, + {handler: shuffle.HandleUpdateUser, path: "/api/v1/users/updateuser", method: "PUT"}, + {handler: shuffle.DeleteUser, path: "/api/v1/users/123", method: "DELETE"}, + {handler: shuffle.HandlePasswordChange, path: "/api/v1/users/passwordchange", method: "POST"}, + {handler: shuffle.HandleGetUsers, path: "/api/v1/users", method: "GET"}, + {handler: shuffle.HandleGetEnvironments, path: "/api/v1/getenvironments", method: "GET"}, + {handler: shuffle.HandleSetEnvironments, path: "/api/v1/setenvironments", method: "PUT"}, + + // handleWorkflowQueue generates nil pointer exception + //{handler: handleWorkflowQueue, path: "/api/v1/streams", method: "POST"}, + // handleGetStreamResults generates nil pointer exception + //{handler: handleGetStreamResults, path: "/api/v1/streams/results", method: "POST"}, + + {handler: handleAppHotloadRequest, path: "/api/v1/apps/run_hotload", method: "GET"}, + {handler: LoadSpecificApps, path: "/api/v1/apps/get_existing", method: "POST"}, + {handler: shuffle.UpdateWorkflowAppConfig, path: "/api/v1/apps/123", method: "PATCH"}, + {handler: validateAppInput, path: "/api/v1/apps/validate", method: "POST"}, + {handler: shuffle.DeleteWorkflowApp, path: "/api/v1/apps/123", method: "DELETE"}, + {handler: shuffle.GetWorkflowAppConfig, path: "/api/v1/apps/123/config", method: "GET"}, + {handler: getWorkflowApps, path: "/api/v1/apps", method: "GET"}, + {handler: setNewWorkflowApp, path: "/api/v1/apps", method: "PUT"}, + //{handler: shuffle.GetSpecificApps, path: "/api/v1/apps/search", method: "POST"}, + + {handler: shuffle.GetAppAuthentication, path: "/api/v1/apps/authentication", method: "GET"}, + {handler: shuffle.AddAppAuthentication, path: "/api/v1/apps/authentication", method: "PUT"}, + {handler: shuffle.DeleteAppAuthentication, path: "/api/v1/apps/authentication/123", method: "DELETE"}, + + {handler: validateAppInput, path: "/api/v1/workflows/apps/validate", method: "POST"}, + {handler: getWorkflowApps, path: "/api/v1/workflows/apps", method: "GET"}, + {handler: setNewWorkflowApp, path: "/api/v1/workflows/apps", method: "PUT"}, + + {handler: shuffle.GetWorkflows, path: "/api/v1/workflows", method: "GET"}, + {handler: shuffle.SetNewWorkflow, path: "/api/v1/workflows", method: "POST"}, + {handler: handleGetWorkflowqueue, path: "/api/v1/workflows/queue", method: "GET"}, + {handler: handleGetWorkflowqueueConfirm, path: "/api/v1/workflows/queue/confirm", method: "POST"}, + {handler: shuffle.HandleGetSchedules, path: "/api/v1/workflows/schedules", method: "GET"}, + {handler: loadSpecificWorkflows, path: "/api/v1/workflows/download_remote", method: "POST"}, + {handler: executeWorkflow, path: "/api/v1/workflows/123/execute", method: "GET"}, + {handler: scheduleWorkflow, path: "/api/v1/workflows/123/schedule", method: "POST"}, + {handler: stopSchedule, path: "/api/v1/workflows/123/schedule/abc", method: "DELETE"}, + // createOutlookSub generates nil pointer exception + {handler: shuffle.HandleCreateOutlookSub, path: "/api/v1/workflows/123/outlook", method: "POST"}, + // handleDeleteOutlookSub generates nil pointer exception + {handler: shuffle.HandleDeleteOutlookSub, path: "/api/v1/workflows/123/outlook/abc", method: "DELETE"}, + {handler: shuffle.GetWorkflowExecutions, path: "/api/v1/workflows/123/executions", method: "GET"}, + {handler: shuffle.AbortExecution, path: "/api/v1/workflows/123/executions/abc/abort", method: "GET"}, + {handler: shuffle.GetSpecificWorkflow, path: "/api/v1/workflows/123", method: "GET"}, + {handler: shuffle.SaveWorkflow, path: "/api/v1/workflows/123", method: "PUT"}, + {handler: deleteWorkflow, path: "/api/v1/workflows/123", method: "DELETE"}, + + {handler: shuffle.HandleNewHook, path: "/api/v1/hooks/new", method: "POST"}, + {handler: handleWebhookCallback, path: "/api/v1/hooks/123", method: "POST"}, + {handler: shuffle.HandleDeleteHook, path: "/api/v1/hooks/123/delete", method: "DELETE"}, + + {handler: shuffle.HandleGetSpecificTrigger, path: "/api/v1/triggers/123", method: "GET"}, + //{handler: shuffle.HandleGetSpecificStats, path: "/api/v1/stats/123", method: "GET"}, + + {handler: verifySwagger, path: "/api/v1/verify_swagger", method: "POST"}, + {handler: verifySwagger, path: "/api/v1/verify_openapi", method: "POST"}, + {handler: echoOpenapiData, path: "/api/v1/get_openapi_uri", method: "POST"}, + {handler: echoOpenapiData, path: "/api/v1/validate_openapi", method: "POST"}, + {handler: shuffle.ValidateSwagger, path: "/api/v1/validate_openapi", method: "POST"}, + {handler: getOpenapi, path: "/api/v1/get_openapi", method: "GET"}, + + //{handler: shuffle.CleanupExecutions, path: "/api/v1/execution_cleanup", method: "GET"}, + + {handler: handleCloudSetup, path: "/api/v1/cloud/setup", method: "POST"}, + {handler: shuffle.HandleGetOrgs, path: "/api/v1/orgs", method: "POST"}, + {handler: shuffle.HandleGetFileContent, path: "/api/v1/files/{fileId}/content", method: "POST"}, + } + + var err error + ctx := context.Background() + + // Most handlers requires database access in order to not crash or cause + // nil pointer issues. + // To start a local database instance, run: + // docker-compose up database + // To let the tests know about the database, run: + // DATASTORE_EMULATOR_HOST=0.0.0.0:8000 go test + dbclient, err = datastore.NewClient(ctx, gceProject, option.WithGRPCDialOption(grpc.WithNoProxy())) + if err != nil { + t.Fatal(err) + } + + dummyBody := bytes.NewBufferString("dummy") + + for _, e := range handlers { + log.Printf("Endpoint: %#v", e.path) + req, err := http.NewRequest(e.method, e.path, dummyBody) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(e.handler) + + timeoutHandler := http.TimeoutHandler(handler, 2*time.Second, `Request Timeout.`) + timeoutHandler.ServeHTTP(rr, req) + + funcName := getFunctionNameFromFunction(e.handler) + if status := rr.Code; status != http.StatusUnauthorized { + t.Errorf("%s handler returned wrong status code: got %v want %v", + funcName, status, http.StatusUnauthorized) + } + } +} + +func TestAuthenticationNotRequired(t *testing.T) { + // All of these return 200 OK when user not logged in + handlers := []endpoint{ + {handler: checkAdminLogin, path: "/api/v1/users/checkusers", method: "GET"}, + {handler: shuffle.HandleLogout, path: "/api/v1/users/logout", method: "POST"}, + {handler: shuffle.GetDocList, path: "/api/v1/docs", method: "GET"}, + {handler: shuffle.GetDocs, path: "/api/v1/docs/123", method: "GET"}, + {handler: healthCheckHandler, path: "/api/v1/_ah/health"}, + } + + for _, e := range handlers { + log.Printf("Endpoint: %#v", e.path) + req, err := http.NewRequest(e.method, e.path, nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + handler := http.HandlerFunc(e.handler) + + timeoutHandler := http.TimeoutHandler(handler, 2*time.Second, `Request Timeout.`) + timeoutHandler.ServeHTTP(rr, req) + + funcName := getFunctionNameFromFunction(e.handler) + if status := rr.Code; status != http.StatusOK { + t.Errorf("%s handler returned wrong status code: got %v want %v", + funcName, status, http.StatusOK) + } + } +} + +// TestCors tests that all endpoints returns the same CORS headers when hit +// with an OPTIONS type request. +// It feels very fragile to test headers like this, especially for the +// "Access-Control-Allow-Origin", but this test should be helpful while +// refactoring the CORS logic into a middleware, and that's the reason this +// test exists right now. It might change after the refactor because our +// requirements might change after the refactor. +func TestCors(t *testing.T) { + handlers := []endpoint{ + {handler: shuffle.HandleNewOutlookRegister, path: "/functions/outlook/register", method: "GET"}, + {handler: shuffle.HandleGetOutlookFolders, path: "/functions/outlook/getFolders", method: "GET"}, + {handler: shuffle.HandleApiGeneration, path: "/api/v1/users/generateapikey", method: "GET"}, + {handler: handleLogin, path: "/api/v1/users/login", method: "POST"}, // prob not this one + // handleRegister generates nil pointer exception + {handler: handleRegister, path: "/api/v1/users/register", method: "POST"}, + {handler: shuffle.HandleGetUsers, path: "/api/v1/users/getusers", method: "GET"}, + {handler: handleInfo, path: "/api/v1/users/getinfo", method: "GET"}, + {handler: shuffle.HandleSettings, path: "/api/v1/users/getsettings", method: "GET"}, + {handler: shuffle.HandleUpdateUser, path: "/api/v1/users/updateuser", method: "PUT"}, + {handler: shuffle.DeleteUser, path: "/api/v1/users/123", method: "DELETE"}, + // handlePasswordChange generates nil pointer exception + {handler: shuffle.HandlePasswordChange, path: "/api/v1/users/passwordchange", method: "POST"}, + {handler: shuffle.HandleGetUsers, path: "/api/v1/users", method: "GET"}, + {handler: shuffle.HandleGetEnvironments, path: "/api/v1/getenvironments", method: "GET"}, + {handler: shuffle.HandleSetEnvironments, path: "/api/v1/setenvironments", method: "PUT"}, + + // handleWorkflowQueue generates nil pointer exception + {handler: handleWorkflowQueue, path: "/api/v1/streams", method: "POST"}, + // handleGetStreamResults generates nil pointer exception + {handler: handleGetStreamResults, path: "/api/v1/streams/results", method: "POST"}, + + {handler: handleAppHotloadRequest, path: "/api/v1/apps/run_hotload", method: "GET"}, + {handler: LoadSpecificApps, path: "/api/v1/apps/get_existing", method: "POST"}, + {handler: shuffle.UpdateWorkflowAppConfig, path: "/api/v1/apps/123", method: "PATCH"}, + {handler: validateAppInput, path: "/api/v1/apps/validate", method: "POST"}, + {handler: shuffle.DeleteWorkflowApp, path: "/api/v1/apps/123", method: "DELETE"}, + {handler: shuffle.GetWorkflowAppConfig, path: "/api/v1/apps/123/config", method: "GET"}, + {handler: getWorkflowApps, path: "/api/v1/apps", method: "GET"}, + {handler: setNewWorkflowApp, path: "/api/v1/apps", method: "PUT"}, + //{handler: shuffle.GetSpecificApps, path: "/api/v1/apps/search", method: "POST"}, + + {handler: shuffle.GetAppAuthentication, path: "/api/v1/apps/authentication", method: "GET"}, + {handler: shuffle.AddAppAuthentication, path: "/api/v1/apps/authentication", method: "PUT"}, + {handler: shuffle.DeleteAppAuthentication, path: "/api/v1/apps/authentication/123", method: "DELETE"}, + + {handler: validateAppInput, path: "/api/v1/workflows/apps/validate", method: "POST"}, + {handler: getWorkflowApps, path: "/api/v1/workflows/apps", method: "GET"}, + {handler: setNewWorkflowApp, path: "/api/v1/workflows/apps", method: "PUT"}, + + {handler: shuffle.GetWorkflows, path: "/api/v1/workflows", method: "GET"}, + {handler: shuffle.SetNewWorkflow, path: "/api/v1/workflows", method: "POST"}, + {handler: handleGetWorkflowqueue, path: "/api/v1/workflows/queue", method: "GET"}, + {handler: handleGetWorkflowqueueConfirm, path: "/api/v1/workflows/queue/confirm", method: "POST"}, + {handler: shuffle.HandleGetSchedules, path: "/api/v1/workflows/schedules", method: "GET"}, + {handler: loadSpecificWorkflows, path: "/api/v1/workflows/download_remote", method: "POST"}, + {handler: executeWorkflow, path: "/api/v1/workflows/123/execute", method: "GET"}, + {handler: scheduleWorkflow, path: "/api/v1/workflows/123/schedule", method: "POST"}, + {handler: stopSchedule, path: "/api/v1/workflows/123/schedule/abc", method: "DELETE"}, + // createOutlookSub generates nil pointer exception + {handler: shuffle.HandleCreateOutlookSub, path: "/api/v1/workflows/123/outlook", method: "POST"}, + // handleDeleteOutlookSub generates nil pointer exception + {handler: shuffle.HandleDeleteOutlookSub, path: "/api/v1/workflows/123/outlook/abc", method: "DELETE"}, + {handler: shuffle.GetWorkflowExecutions, path: "/api/v1/workflows/123/executions", method: "GET"}, + {handler: shuffle.AbortExecution, path: "/api/v1/workflows/123/executions/abc/abort", method: "GET"}, + {handler: shuffle.GetSpecificWorkflow, path: "/api/v1/workflows/123", method: "GET"}, + {handler: shuffle.SaveWorkflow, path: "/api/v1/workflows/123", method: "PUT"}, + {handler: deleteWorkflow, path: "/api/v1/workflows/123", method: "DELETE"}, + + {handler: shuffle.HandleNewHook, path: "/api/v1/hooks/new", method: "POST"}, + {handler: handleWebhookCallback, path: "/api/v1/hooks/123", method: "POST"}, + {handler: shuffle.HandleDeleteHook, path: "/api/v1/hooks/123/delete", method: "DELETE"}, + + {handler: shuffle.HandleGetSpecificTrigger, path: "/api/v1/triggers/123", method: "GET"}, + //{handler: shuffle.HandleGetSpecificStats, path: "/api/v1/stats/123", method: "GET"}, + + {handler: verifySwagger, path: "/api/v1/verify_swagger", method: "POST"}, + {handler: verifySwagger, path: "/api/v1/verify_openapi", method: "POST"}, + {handler: echoOpenapiData, path: "/api/v1/get_openapi_uri", method: "POST"}, + {handler: echoOpenapiData, path: "/api/v1/validate_openapi", method: "POST"}, + {handler: shuffle.ValidateSwagger, path: "/api/v1/validate_openapi", method: "POST"}, + {handler: getOpenapi, path: "/api/v1/get_openapi", method: "GET"}, + + //{handler: shuffle.CleanupExecutions, path: "/api/v1/execution_cleanup", method: "GET"}, + + {handler: handleCloudSetup, path: "/api/v1/cloud/setup", method: "POST"}, + {handler: shuffle.HandleGetOrgs, path: "/api/v1/orgs", method: "POST"}, + {handler: shuffle.HandleGetFileContent, path: "/api/v1/files/{fileId}/content", method: "POST"}, + } + + //r := initHandlers(context.TODO()) + initHandlers() + +outerLoop: + for _, e := range handlers { + log.Printf("Endpoint: %#v", e.path) + req, err := http.NewRequest("OPTIONS", e.path, nil) + req.Header.Add("Origin", "http://localhost:3000") + req.Header.Add("Access-Control-Request-Method", "POST") + req.Header.Add("Access-Control-Request-Headers", "Content-Type, Accept, X-Requested-With, remember-me") + + // OPTIONS /resource/foo + // Access-Control-Request-Method: DELETE + // Access-Control-Request-Headers: origin, x-requested-with + // Origin: https://foo.bar.org + + if err != nil { + t.Errorf("Failure in OPTIONS setup: %s", err) + continue + } + + rr := httptest.NewRecorder() + + //timeoutHandler := http.TimeoutHandler(r, 2*time.Second, `Request Timeout`) + //timeoutHandler.ServeHTTP(rr, req) + + funcName := getFunctionNameFromFunction(e.handler) + if status := rr.Code; status != http.StatusOK { + t.Errorf("%s handler returned wrong status code: got %v want %v", + funcName, status, http.StatusOK) + continue + } + + want := map[string]string{ + "Vary": "Origin", + "Access-Control-Allow-Headers": "Content-Type, Accept, X-Requested-With, Remember-Me", + "Access-Control-Allow-Methods": "POST", + "Access-Control-Allow-Credentials": "true", + "Access-Control-Allow-Origin": "http://localhost:3000", + } + + // Remember to use canonical header name if accessing the headers array + // directly: + // v := r.Header[textproto.CanonicalMIMEHeaderKey("foo")] + // When using Header().Get(h), h will automatically be converted to canonical format. + + for key, value := range want { + got := rr.Header().Get(key) + if got != value { + t.Errorf("%s handler returned wrong value for '%s' header: got '%v' want '%v'", + funcName, key, got, value) + continue outerLoop + } + } + + } +} + +func getFunctionNameFromFunction(f interface{}) string { + return runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name() +} diff --git a/backend/go-app/oauth2.go b/backend/go-app/oauth2.go deleted file mode 100644 index 773661f3..00000000 --- a/backend/go-app/oauth2.go +++ /dev/null @@ -1,1033 +0,0 @@ -package main - -import ( - "github.com/frikky/shuffle-shared" - - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "log" - "net/http" - "net/url" - "strings" - "time" - - "golang.org/x/oauth2" -) - -func getOutlookAttachment(client *http.Client, emailId, attachmentId string) ([]shuffle.FullEmail, error) { - //requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/users/ec03b4f2-fccf-4c35-b0eb-be85a0f5dd43/mailFolders") - - requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/%s/attachments/%s", emailId, attachmentId) - //log.Printf("Outlook email URL: %#v", requestUrl) - - ret, err := client.Get(requestUrl) - if err != nil { - log.Printf("[INFO] OutlookErr: %s", err) - return []shuffle.FullEmail{}, err - } - - body, err := ioutil.ReadAll(ret.Body) - if err != nil { - log.Printf("[WARNING] Failed body decoding from outlook email") - return []shuffle.FullEmail{}, err - } - - //type FullEmail struct { - log.Printf("[INFO] Attachment Body: %s", string(body)) - log.Printf("[INFO] Status email: %d", ret.StatusCode) - if ret.StatusCode != 200 { - return []shuffle.FullEmail{}, err - } - - //log.Printf("Body: %s", string(body)) - - /* - parsedmail := FullEmail{} - err = json.Unmarshal(body, &parsedmail) - if err != nil { - log.Printf("[INFO] Email unmarshal error: %s", err) - return []FullEmail{}, err - } - - emails = append(emails, parsedmail) - */ - - return []shuffle.FullEmail{}, nil -} - -func getOutlookEmail(client *http.Client, maildata shuffle.MailData) ([]shuffle.FullEmail, error) { - //requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/users/ec03b4f2-fccf-4c35-b0eb-be85a0f5dd43/mailFolders") - - emails := []shuffle.FullEmail{} - for _, email := range maildata.Value { - //messageId := email.Resourcedata.ID - //requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/me/%s", messageId) - requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/%s", email.Resource) - //log.Printf("Outlook email URL: %#v", requestUrl) - - ret, err := client.Get(requestUrl) - if err != nil { - log.Printf("[INFO] OutlookErr: %s", err) - return []shuffle.FullEmail{}, err - } - - body, err := ioutil.ReadAll(ret.Body) - if err != nil { - log.Printf("[WARNING] Failed body decoding from outlook email") - return []shuffle.FullEmail{}, err - } - - //type shuffle.FullEmail struct { - //log.Printf("[INFO] EMAIL Body: %s", string(body)) - //log.Printf("[INFO] Status email: %d", ret.StatusCode) - if ret.StatusCode != 200 { - return []shuffle.FullEmail{}, err - } - - //log.Printf("Body: %s", string(body)) - - parsedmail := shuffle.FullEmail{} - err = json.Unmarshal(body, &parsedmail) - if err != nil { - log.Printf("[INFO] Email unmarshal error: %s", err) - return []shuffle.FullEmail{}, err - } - - emails = append(emails, parsedmail) - } - - return emails, nil -} - -func getOutlookProfile(client *http.Client) (shuffle.OutlookProfile, error) { - requestUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/me?$select=mail") - - ret, err := client.Get(requestUrl) - if err != nil { - log.Printf("[INFO] Folder error: %s", err) - return shuffle.OutlookProfile{}, err - } - - log.Printf("[INFO] Status profile: %d", ret.StatusCode) - body, err := ioutil.ReadAll(ret.Body) - if err != nil { - log.Printf("[INFO] Body: %s", err) - return shuffle.OutlookProfile{}, err - } - - log.Printf("[INFO] BODY: %s", string(body)) - - profile := shuffle.OutlookProfile{} - err = json.Unmarshal(body, &profile) - if err != nil { - log.Printf("Unmarshal: %s", err) - return shuffle.OutlookProfile{}, err - } - - return profile, nil -} - -func handleNewOutlookRegister(resp http.ResponseWriter, request *http.Request) { - cors := shuffle.HandleCors(resp, request) - if cors { - return - } - - user, err := shuffle.HandleApiAuthentication(resp, request) - if err != nil { - log.Printf("[INFO] Api authentication failed in getting specific trigger: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - code := request.URL.Query().Get("code") - if len(code) == 0 { - log.Println("No code") - resp.WriteHeader(401) - return - } - - url := fmt.Sprintf("http://%s%s", request.Host, request.URL.EscapedPath()) - log.Println(url) - ctx := context.Background() - _, accessToken, err := getOutlookClient(ctx, code, shuffle.OauthToken{}, url) - if err != nil { - log.Printf("Oauth client failure - outlook register: %s", err) - resp.WriteHeader(401) - return - } - - // This should be possible, and will also give the actual username - - /* - profile, err := getOutlookProfile(client) - if err != nil { - log.Printf("Outlook profile failure: %s", err) - resp.WriteHeader(401) - return - } - */ - - // This is a state workaround, which should really be for CSRF checks lol - state := request.URL.Query().Get("state") - if len(state) == 0 { - log.Println("No state") - resp.WriteHeader(401) - return - } - - stateitems := strings.Split(state, "%26") - if len(stateitems) == 1 { - stateitems = strings.Split(state, "&") - } - - // FIXME - trigger auth - senderUser := "" - trigger := shuffle.TriggerAuth{} - for _, item := range stateitems { - itemsplit := strings.Split(item, "%3D") - if len(itemsplit) == 1 { - itemsplit = strings.Split(item, "=") - } - - if len(itemsplit) != 2 { - continue - } - - //log.Printf("ITEM: %#v", itemsplit) - - // Do something here - if itemsplit[0] == "workflow_id" { - trigger.WorkflowId = itemsplit[1] - } else if itemsplit[0] == "trigger_id" { - trigger.Id = itemsplit[1] - } else if itemsplit[0] == "type" { - trigger.Type = itemsplit[1] - } else if itemsplit[0] == "start" { - trigger.Start = itemsplit[1] - } else if itemsplit[0] == "username" { - trigger.Username = itemsplit[1] - trigger.Owner = itemsplit[1] - senderUser = itemsplit[1] - } - } - - // THis is an override based on the user in oauth return - /* - if len(profile.Mail) > 0 { - trigger.Username = profile.Mail - } - */ - - trigger.Code = code - trigger.OauthToken = shuffle.OauthToken{ - AccessToken: accessToken.AccessToken, - TokenType: accessToken.TokenType, - RefreshToken: accessToken.RefreshToken, - Expiry: accessToken.Expiry, - } - - //log.Printf("%#v", trigger) - log.Println(trigger.WorkflowId) - log.Println(trigger.Id) - log.Println(senderUser) - log.Println(trigger.Type) - log.Printf("STARTNODE: %s", trigger.Start) - log.Printf("[INFO] Attempting to set up outlook trigger for %s", senderUser) - if trigger.WorkflowId == "" || trigger.Id == "" || senderUser == "" || trigger.Type == "" { - log.Printf("[INFO] All oauth items need to contain data to register a new state") - resp.WriteHeader(401) - return - } - - // Should also update the user - Userdata, err := shuffle.GetUser(ctx, user.Id) - if err != nil { - log.Printf("[INFO] Username %s doesn't exist (oauth2): %s", trigger.Username, err) - resp.WriteHeader(401) - return - } - - Userdata.Authentication = append(Userdata.Authentication, shuffle.UserAuth{ - Name: "Outlook", - Description: "oauth2", - Workflows: []string{trigger.WorkflowId}, - Username: trigger.Username, - Fields: []shuffle.UserAuthField{ - shuffle.UserAuthField{ - Key: "trigger_id", - Value: trigger.Id, - }, - shuffle.UserAuthField{ - Key: "username", - Value: trigger.Username, - }, - shuffle.UserAuthField{ - Key: "code", - Value: code, - }, - shuffle.UserAuthField{ - Key: "type", - Value: trigger.Type, - }, - }, - }) - - // Set apikey for the user if they don't have one - err = shuffle.SetUser(ctx, Userdata, true) - if err != nil { - log.Printf("Failed setting user data for %s: %s", Userdata.Username, err) - resp.WriteHeader(401) - return - } - - err = shuffle.SetTriggerAuth(ctx, trigger) - if err != nil { - log.Printf("Failed to set trigger auth for %s - %s", trigger.Username, err) - resp.WriteHeader(401) - return - } - - resp.WriteHeader(200) - resp.Write([]byte(`{"success": true}`)) -} - -// THis all of a sudden became really horrible.. fml -func getOutlookClient(ctx context.Context, code string, accessToken shuffle.OauthToken, redirectUri string) (*http.Client, *oauth2.Token, error) { - - conf := &oauth2.Config{ - ClientID: "fd55c175-aa30-4fa6-b303-09a29fb3f750", - ClientSecret: "14OBKgUpov.D7fe0~hp0z-cIQdP~SlYm.8", - Scopes: []string{ - "Mail.Read", - }, - RedirectURL: redirectUri, - Endpoint: oauth2.Endpoint{ - AuthURL: "https://login.microsoftonline.com/common/oauth2/authorize", - TokenURL: "https://login.microsoftonline.com/common/oauth2/token", - }, - } - - if len(code) > 0 { - access_token, err := conf.Exchange(ctx, code) - if err != nil { - log.Printf("Access_token issue: %s", err) - return &http.Client{}, access_token, err - } - - client := conf.Client(ctx, access_token) - return client, access_token, nil - } - - // Manually recreate the oauthtoken - access_token := &oauth2.Token{ - AccessToken: accessToken.AccessToken, - TokenType: accessToken.TokenType, - RefreshToken: accessToken.RefreshToken, - Expiry: accessToken.Expiry, - } - - client := conf.Client(ctx, access_token) - return client, access_token, nil -} - -func handleGetSpecificTrigger(resp http.ResponseWriter, request *http.Request) { - cors := handleCors(resp, request) - if cors { - return - } - - user, err := shuffle.HandleApiAuthentication(resp, request) - if err != nil { - log.Printf("Api authentication failed in getting specific workflow: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - location := strings.Split(request.URL.String(), "/") - - var workflowId string - if location[1] == "api" { - if len(location) <= 4 { - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - workflowId = location[5] - } - - if strings.Contains(workflowId, "?") { - workflowId = strings.Split(workflowId, "?")[0] - } - - ctx := context.Background() - trigger, err := shuffle.GetTriggerAuth(ctx, workflowId) - if err != nil { - log.Printf("[INFO] Trigger %s doesn't exist - specific trigger.", workflowId) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": ""}`)) - return - } - - if user.Username != trigger.Owner && user.Role != "admin" { - log.Printf("Wrong user (%s) for trigger %s", user.Username, trigger.Id) - resp.WriteHeader(401) - return - } - - trigger.OauthToken = shuffle.OauthToken{} - trigger.Code = "" - - b, err := json.Marshal(trigger) - if err != nil { - log.Println("Failed to marshal data") - resp.WriteHeader(401) - return - } - - resp.WriteHeader(200) - resp.Write(b) -} - -// Lists the users current subscriptions -func getOutlookSubscriptions(outlookClient *http.Client) (SubscriptionsWrapper, error) { - fullUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/subscriptions") - req, err := http.NewRequest( - "GET", - fullUrl, - nil, - ) - req.Header.Add("Content-Type", "application/json") - res, err := outlookClient.Do(req) - if err != nil { - log.Printf("suberror Client: %s", err) - return SubscriptionsWrapper{}, err - } - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - log.Printf("Suberror Body: %s", err) - return SubscriptionsWrapper{}, err - } - - newSubs := SubscriptionsWrapper{} - err = json.Unmarshal(body, &newSubs) - if err != nil { - return SubscriptionsWrapper{}, err - } - - return newSubs, nil -} - -type SubscriptionsWrapper struct { - OdataContext string `json:"@odata.context"` - Value []Subscription `json:"value"` -} - -type Subscription struct { - ChangeType string `json:"changeType"` - NotificationURL string `json:"notificationUrl"` - Resource string `json:"resource"` - ExpirationDateTime string `json:"expirationDateTime"` - ClientState string `json:"clientState"` - Id string `json:"id"` -} - -func makeOutlookSubscription(client *http.Client, folderIds []string, notificationURL string) (string, error) { - fullUrl := "https://graph.microsoft.com/v1.0/subscriptions" - - // FIXME - this expires rofl - t := time.Now().Local().Add(time.Minute * time.Duration(4200)) - timeFormat := fmt.Sprintf("%d-%02d-%02dT%02d:%02d:%02d.0000000Z", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second()) - - resource := fmt.Sprintf("me/mailfolders('%s')/messages", strings.Join(folderIds, "','")) - log.Printf("[INFO] Subscription resource to get(s): %s", resource) - sub := Subscription{ - ChangeType: "created", - ClientState: "Shuffle subscription", - NotificationURL: notificationURL, - ExpirationDateTime: timeFormat, - Resource: resource, - } - //ClientState: "This is a test", - - data, err := json.Marshal(sub) - if err != nil { - log.Printf("Marshal: %s", err) - return "", err - } - - req, err := http.NewRequest( - "POST", - fullUrl, - bytes.NewBuffer(data), - ) - req.Header.Add("Content-Type", "application/json") - - res, err := client.Do(req) - if err != nil { - log.Printf("Client: %s", err) - return "", err - } - - log.Printf("[INFO] Subscription Status: %d", res.StatusCode) - body, err := ioutil.ReadAll(res.Body) - if err != nil { - log.Printf("Body: %s", err) - return "", err - } - - if res.StatusCode != 200 && res.StatusCode != 201 { - return "", errors.New(fmt.Sprintf("Subscription failed: %s", string(body))) - } - - // Use data from body here to create thingy - newSub := Subscription{} - err = json.Unmarshal(body, &newSub) - if err != nil { - return "", err - } - - return newSub.Id, nil -} - -// Basically the same as a webhook -func handleOutlookCallback(resp http.ResponseWriter, request *http.Request) { - path := strings.Split(request.URL.String(), "/") - if len(path) < 4 { - log.Printf("[INFO] Bad outlook callback URL: %s", path) - resp.WriteHeader(403) - resp.Write([]byte(`{"success": false}`)) - return - } - - // 1. Get config with hookId - //fmt.Sprintf("%s/api/v1/hooks/%s", callbackUrl, hookId) - ctx := context.Background() - location := strings.Split(request.URL.String(), "/") - - var hookId string - if location[1] == "api" { - if len(location) <= 4 { - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - hookId = location[5] - } - - // ID: webhook_ - if len(hookId) != 36 { - log.Printf("[WARNING] Bad hook ID: %s (%d)", hookId, len(hookId)) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "message": "ID not valid"}`)) - return - } - - //func getTriggerAuth(ctx context.Context, id string) (*TriggerAuth, error) { - hook, err := shuffle.GetTriggerAuth(ctx, hookId) - if err != nil { - log.Printf("[INFO] Failed getting trigger %s (callback): %s", hookId, err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - body, err := ioutil.ReadAll(request.Body) - if err != nil { - log.Printf("[INFO] Body data error: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - //log.Printf("[INFO] BODY: %s. Len: %d", string(body), len(body)) - //key, ok := request.URL.Query()["validationToken"] - //if ok { - //} - token := request.URL.Query().Get("validationToken") - if len(body) == 0 && len(token) > 0 { - log.Printf("[INFO] Should handle trigger token %s", token) - resp.WriteHeader(200) - resp.Write([]byte(string(token))) - return - } - - // 1. Take the body and parse data -> Get the email itself - - maildata := shuffle.MailData{} - err = json.Unmarshal(body, &maildata) - if err != nil { - log.Printf("Maildata unmarshal error: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - redirectDomain := "localhost:5001" - redirectUrl := fmt.Sprintf("http://%s/api/v1/triggers/outlook/register", redirectDomain) - outlookClient, _, err := getOutlookClient(ctx, "", hook.OauthToken, redirectUrl) - if err != nil { - log.Printf("Oauth client failure - triggerauth: %s", err) - resp.WriteHeader(401) - return - } - - emails, err := getOutlookEmail(outlookClient, maildata) - log.Printf("[INFO] EMAILS: %d. If this is more than 1, please contact frikky@shuffler.io", len(emails)) - //log.Printf("INSIDE GET OUTLOOK EMAIL!: %#v, %s", emails, err) - - //type shuffle.FullEmail struct { - email := shuffle.FullEmail{} - if len(emails) == 1 { - email = emails[0] - } - - // Parse indicators (domains, emails, ips, domains etc)! - newEmail := shuffle.ParsedShuffleMail{} - newEmail.Body.ContentType = email.Body.Contenttype - newEmail.Body.Content = email.Body.Content - newEmail.Body.RawBody = email.Body.Content - - newEmail.Header.Subject = email.Subject - newEmail.Header.From = email.From.Emailaddress.Address - for _, to := range email.Torecipients { - newEmail.Header.To = append(newEmail.Header.To, to.Emailaddress.Address) - } - newEmail.Header.Date = email.Receiveddatetime.String() - - newEmail.MessageID = email.ID - - if email.Hasattachments { - log.Printf("SHOULD HANDLE ATTACHMENTS FOR EMAIL!") - - for _, attachment := range email.Attachments { - parsedAttachment, err := getOutlookAttachment(outlookClient, email.ID, attachment.ID) - if err != nil { - log.Printf("Failed attachment %s: %s", attachment.ID, err) - continue - } - - log.Printf("ATTACHMENT: %#v", parsedAttachment) - } - //log.Printf("%#v", attachments) - //log.Printf("%s", err) - //GET /users/{id | userPrincipalName}/events/{id}/attachments/{id} - } - - emailBytes, err := json.Marshal(email) - if err != nil { - log.Printf("[INFO] Failed email marshaling: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - type ExecutionStruct struct { - Start string `json:"start"` - ExecutionSource string `json:"execution_source"` - ExecutionArgument string `json:"execution_argument"` - } - - newBody := ExecutionStruct{ - Start: hook.Start, - ExecutionSource: "outlook", - ExecutionArgument: string(emailBytes), - } - - b, err := json.Marshal(newBody) - if err != nil { - log.Printf("[INFO] Failed newBody marshaling: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - baseUrl := &url.URL{} - newRequest := &http.Request{ - URL: baseUrl, - Method: "POST", - Body: ioutil.NopCloser(bytes.NewReader(b)), - } - - workflow := shuffle.Workflow{ - ID: "", - } - - // OrgId: activeOrgs[0].Id, - workflowExecution, executionResp, err := handleExecution(hook.WorkflowId, workflow, newRequest) - if err == nil { - resp.WriteHeader(200) - resp.Write([]byte(fmt.Sprintf(`{"success": true, "execution_id": "%s", "authorization": "%s"}`, workflowExecution.ExecutionId, workflowExecution.Authorization))) - return - } - - resp.WriteHeader(500) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, executionResp))) -} - -func removeOutlookSubscription(outlookClient *http.Client, subscriptionId string) error { - // DELETE https://graph.microsoft.com/v1.0/subscriptions/{id} - fullUrl := fmt.Sprintf("https://graph.microsoft.com/v1.0/subscriptions/%s", subscriptionId) - req, err := http.NewRequest( - "DELETE", - fullUrl, - nil, - ) - req.Header.Add("Content-Type", "application/json") - res, err := outlookClient.Do(req) - if err != nil { - log.Printf("Client: %s", err) - return err - } - - if res.StatusCode != 200 && res.StatusCode != 201 && res.StatusCode != 204 { - return errors.New(fmt.Sprintf("Bad status code when deleting subscription: %d", res.StatusCode)) - } - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - log.Printf("Body: %s", err) - return err - } - - _ = body - - return nil -} - -// Remove AUTH -// Remove function -// Remove subscription -func handleOutlookSubRemoval(ctx context.Context, user shuffle.User, workflowId, triggerId string) error { - // 1. Get the auth for trigger - // 2. Stop the subscription - // 3. Remove the function - // 4. Remove the database entry for auth - trigger, err := shuffle.GetTriggerAuth(ctx, triggerId) - if err != nil { - log.Printf("Trigger auth %s doesn't exist - outlook sub removal.", triggerId) - return err - } - - if runningEnvironment != "cloud" { - log.Printf("[INFO] SHOULD STOP OUTLOOK SUB ONPREM SYNC WITH CLOUD for workflow ID %s", workflowId) - org, err := shuffle.GetOrg(ctx, user.ActiveOrg.Id) - if err != nil { - log.Printf("[INFO] Failed finding org %s during outlook removal: %s", org.Id, err) - return err - } - - log.Printf("[INFO] Stopping cloud configuration for trigger %s in org %s for workflow %s", trigger.Id, org.Id, trigger.WorkflowId) - action := shuffle.CloudSyncJob{ - Type: "outlook", - Action: "stop", - OrgId: org.Id, - PrimaryItemId: trigger.Id, - SecondaryItem: trigger.Start, - ThirdItem: trigger.WorkflowId, - } - - err = executeCloudAction(action, org.SyncConfig.Apikey) - if err != nil { - log.Printf("[INFO] Failed cloud action STOP outlook execution: %s", err) - return err - } else { - log.Printf("[INFO] Successfully set STOPPED outlook execution trigger") - } - } else { - log.Printf("SHOULD STOP OUTLOOK SUB IN CLOUD") - } - - // Actually delete the thing - redirectDomain := "localhost:5001" - url := fmt.Sprintf("http://%s/api/v1/triggers/outlook/register", redirectDomain) - outlookClient, _, err := getOutlookClient(ctx, "", trigger.OauthToken, url) - if err != nil { - log.Printf("[WARNING] Oauth client failure - outlook folders: %s", err) - return err - } - notificationURL := fmt.Sprintf("%s/api/v1/hooks/webhook_%s", syncSubUrl, trigger.Id) - curSubscriptions, err := getOutlookSubscriptions(outlookClient) - if err == nil { - for _, sub := range curSubscriptions.Value { - if sub.NotificationURL == notificationURL { - log.Printf("[INFO] Removing subscription %s from o365 for workflow %s", sub.Id, workflowId) - removeOutlookSubscription(outlookClient, sub.Id) - } - } - } else { - log.Printf("Failed to get subscriptions - need to overwrite") - } - - return nil -} - -func handleDeleteOutlookSub(resp http.ResponseWriter, request *http.Request) { - cors := handleCors(resp, request) - if cors { - return - } - - location := strings.Split(request.URL.String(), "/") - - var workflowId string - var triggerId string - if location[1] == "api" { - if len(location) <= 6 { - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - workflowId = location[4] - triggerId = location[6] - } - - if len(workflowId) == 0 || len(triggerId) == 0 { - log.Printf("Ids can't be zero when deleting %s", workflowId) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - ctx := context.Background() - workflow, err := shuffle.GetWorkflow(ctx, workflowId) - if err != nil { - log.Printf("[WARNING] Failed getting the workflow locally (delete outlook): %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - user, err := shuffle.HandleApiAuthentication(resp, request) - if err != nil { - log.Printf("Api authentication failed in outlook deploy: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - // FIXME - have a check for org etc too.. - if user.Id != workflow.Owner && user.Role != "admin" { - log.Printf("Wrong user (%s) for workflow %s when deploying outlook", user.Username, workflow.ID) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - // Check what kind of sub it is - err = handleOutlookSubRemoval(ctx, user, workflowId, triggerId) - if err != nil { - log.Printf("Failed sub removal: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - resp.WriteHeader(200) - resp.Write([]byte(`{"success": true}`)) -} - -// This sets up the sub with outlook itself -// Parses data from the workflow to see whether access is right to subscribe it -// Creates the cloud function for outlook return -// Wait for it to be available, then schedule a workflow to it -func createOutlookSub(resp http.ResponseWriter, request *http.Request) { - cors := handleCors(resp, request) - if cors { - return - } - - location := strings.Split(request.URL.String(), "/") - - var workflowId string - if location[1] == "api" { - if len(location) <= 4 { - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - workflowId = location[4] - } - - ctx := context.Background() - workflow, err := shuffle.GetWorkflow(ctx, workflowId) - if err != nil { - log.Printf("[WARNING] Failed getting the workflow locally (outlook sub): %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - user, err := shuffle.HandleApiAuthentication(resp, request) - if err != nil { - log.Printf("Api authentication failed in outlook deploy: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - // FIXME - have a check for org etc too.. - if user.Id != workflow.Owner && user.Role != "admin" { - log.Printf("Wrong user (%s) for workflow %s when deploying outlook", user.Username, workflow.ID) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - log.Println("[INFO] Handle outlook subscription for trigger") - - // Should already be authorized at this point, as the workflow is shared - body, err := ioutil.ReadAll(request.Body) - if err != nil { - log.Printf("Failed body read for workflow %s", workflow.ID) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - // Based on the input data from frontend - type CurTrigger struct { - Name string `json:"name"` - Folders []string `json:"folders"` - ID string `json:"id"` - } - - //log.Println(string(body)) - var curTrigger CurTrigger - err = json.Unmarshal(body, &curTrigger) - if err != nil { - log.Printf("Failed body read unmarshal for trigger %s", workflow.ID) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - if len(curTrigger.Folders) == 0 { - log.Printf("Error for %s. Choosing folders is required, currently 0", workflow.ID) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - // Now that it's deployed - wait a few seconds before generating: - // 1. Oauth2 token thingies for outlook.office.com - // 2. Set the url to have the right mailboxes (probably ID?) ("https://outlook.office.com/api/v2.0/me/mailfolders('inbox')/messages") - // 3. Set the callback URL to be the new trigger - // 4. Run subscription test - // 5. Set the subscriptionId to the trigger object - - // First - lets regenerate an oauth token for outlook.office.com from the original items - trigger, err := shuffle.GetTriggerAuth(ctx, curTrigger.ID) - if err != nil { - log.Printf("[INFO] Trigger %s doesn't exist - outlook sub.", curTrigger.ID) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false, "reason": ""}`)) - return - } - - // url doesn't really matter here - //url := fmt.Sprintf("https://shuffler.io") - redirectDomain := "localhost:5001" - url := fmt.Sprintf("http://%s/api/v1/triggers/outlook/register", redirectDomain) - outlookClient, _, err := getOutlookClient(ctx, "", trigger.OauthToken, url) - if err != nil { - log.Printf("Oauth client failure - triggerauth: %s", err) - resp.Write([]byte(`{"success": false, "reason": ""}`)) - resp.WriteHeader(401) - return - } - - // Location + - - // This is here simply to let the function start - // Usually takes 10 attempts minimum :O - // 10 * 5 = 50 seconds. That's waaay too much :( - - if runningEnvironment != "cloud" { - org, err := shuffle.GetOrg(ctx, user.ActiveOrg.Id) - if err != nil { - log.Printf("Failed finding org %s: %s", org.Id, err) - return - } - log.Printf("[INFO] Starting cloud configuration TO START trigger %s in org %s for workflow %s", trigger.Id, org.Id, trigger.WorkflowId) - - action := shuffle.CloudSyncJob{ - Type: "outlook", - Action: "start", - OrgId: org.Id, - PrimaryItemId: trigger.Id, - SecondaryItem: trigger.Start, - ThirdItem: workflowId, - } - - err = executeCloudAction(action, org.SyncConfig.Apikey) - if err != nil { - log.Printf("[INFO] Failed cloud action START outlook execution: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) - return - } else { - log.Printf("[INFO] Successfully set up cloud action trigger") - } - } else { - log.Printf("Should configure a running environment for CLOUD") - } - - notificationURL := fmt.Sprintf("%s/api/v1/hooks/webhook_%s", syncSubUrl, trigger.Id) - curSubscriptions, err := getOutlookSubscriptions(outlookClient) - if err == nil { - for _, sub := range curSubscriptions.Value { - if sub.NotificationURL == notificationURL { - log.Printf("[INFO] Removing existing subscription %s", sub.Id) - removeOutlookSubscription(outlookClient, sub.Id) - } - } - } else { - log.Printf("[INFO] Failed to get subscriptions - need to overwrite") - } - - maxFails := 5 - failCnt := 0 - log.Println(curTrigger.Folders) - for { - subId, err := makeOutlookSubscription(outlookClient, curTrigger.Folders, notificationURL) - if err != nil { - failCnt += 1 - log.Printf("Failed making oauth subscription, retrying in 5 seconds: %s", err) - time.Sleep(5 * time.Second) - if failCnt == maxFails { - log.Printf("Failed to set up subscription %d times.", maxFails) - resp.WriteHeader(401) - return - } - - continue - } - - // Set the ID somewhere here - trigger.SubscriptionId = subId - err = shuffle.SetTriggerAuth(ctx, *trigger) - if err != nil { - log.Printf("Failed setting triggerauth: %s", err) - } - - break - } - - log.Printf("[INFO] Successfully handled outlook subscription for trigger %s in workflow %s", curTrigger.ID, workflow.ID) - - //log.Printf("%#v", user) - resp.WriteHeader(200) - resp.Write([]byte(`{"success": true}`)) -} diff --git a/backend/go-app/run.sh b/backend/go-app/run.sh new file mode 100755 index 00000000..2ebf72c9 --- /dev/null +++ b/backend/go-app/run.sh @@ -0,0 +1 @@ +go run main.go walkoff.go docker.go diff --git a/backend/go-app/walkoff.go b/backend/go-app/walkoff.go index c0f16bbf..7078110e 100644 --- a/backend/go-app/walkoff.go +++ b/backend/go-app/walkoff.go @@ -1,7 +1,7 @@ package main import ( - "github.com/frikky/shuffle-shared" + "github.com/shuffle/shuffle-shared" "bytes" "context" @@ -20,9 +20,9 @@ import ( "time" "github.com/docker/docker/api/types" - "github.com/docker/docker/client" + dockerclient "github.com/docker/docker/client" + //gyaml "github.com/ghodss/yaml" - gyaml "github.com/ghodss/yaml" "github.com/h2non/filetype" uuid "github.com/satori/go.uuid" @@ -39,6 +39,7 @@ import ( //"google.golang.org/appengine/memcache" //"cloud.google.com/go/firestore" // "google.golang.org/api/option" + gyaml "github.com/ghodss/yaml" ) var localBase = "http://localhost:5001" @@ -506,7 +507,7 @@ func createSchedule(ctx context.Context, scheduleId, workflowId, name, startNode //log.Printf("BODY: %s", string(body)) parsedArgument := strings.Replace(string(body), "\"", "\\\"", -1) bodyWrapper := fmt.Sprintf(`{"start": "%s", "execution_source": "schedule", "execution_argument": "%s"}`, startNode, parsedArgument) - log.Printf("WRAPPER BODY: \n%s", bodyWrapper) + log.Printf("[INFO] Body for schedule %s in workflow %s: \n%s", scheduleId, workflowId, bodyWrapper) job := func() { request := &http.Request{ URL: &url.URL{}, @@ -628,7 +629,7 @@ func handleGetWorkflowqueueConfirm(resp http.ResponseWriter, request *http.Reque if err != nil { log.Printf("[ERROR] Failed deleting %d execution keys for org %s", len(ids), id) } else { - log.Printf("[INFO] Deleted %d keys from org %s", len(ids), parsedId) + //log.Printf("[INFO] Deleted %d keys from org %s", len(ids), parsedId) } //var newExecutionRequests ExecutionRequestWrapper @@ -687,7 +688,7 @@ func handleGetWorkflowqueue(resp http.ResponseWriter, request *http.Request) { if len(executionRequests.Data) == 0 { executionRequests.Data = []shuffle.ExecutionRequest{} } else { - log.Printf("[INFO] Executionrequests (%s): %d", id, len(executionRequests.Data)) + //log.Printf("[INFO] Executionrequests (%s): %d", id, len(executionRequests.Data)) //log.Printf("IDS: %#v", executionRequests.Data[0].ExecutionId) } @@ -708,6 +709,11 @@ func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) { return } + if request.Body == nil { + resp.WriteHeader(http.StatusBadRequest) + return + } + body, err := ioutil.ReadAll(request.Body) if err != nil { log.Println("Failed reading body for stream result queue") @@ -729,7 +735,7 @@ func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) { ctx := context.Background() workflowExecution, err := shuffle.GetWorkflowExecution(ctx, actionResult.ExecutionId) if err != nil { - //log.Printf("Failed getting execution (streamresult) %s: %s", actionResult.ExecutionId, err) + log.Printf("Failed getting execution (streamresult) %s: %s", actionResult.ExecutionId, err) resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Bad authorization key or execution_id might not exist."}`))) return @@ -755,99 +761,31 @@ func handleGetStreamResults(resp http.ResponseWriter, request *http.Request) { } -// Checks if data is sent from Worker >0.8.51, which sends a full execution -// instead of individial results -func validateNewWorkerExecution(body []byte) error { - ctx := context.Background() - var execution shuffle.WorkflowExecution - err := json.Unmarshal(body, &execution) - if err != nil { - log.Printf("[WARNING] Failed execution unmarshaling: %s", err) - return err - } - //log.Printf("\n\nGOT EXEC WITH RESULT %#v (%d)\n\n", execution.Status, len(execution.Results)) - - baseExecution, err := shuffle.GetWorkflowExecution(ctx, execution.ExecutionId) - if err != nil { - log.Printf("[ERROR] Failed getting execution (workflowqueue) %s: %s", execution.ExecutionId, err) - return err - } - - if baseExecution.Authorization != execution.Authorization { - return errors.New("Bad authorization when validating execution") - } - - // used to validate if it's actually the right marshal - if len(baseExecution.Workflow.Actions) != len(execution.Workflow.Actions) { - return errors.New(fmt.Sprintf("Bad length of actions (probably normal app): %d", len(execution.Workflow.Actions))) - } - - if len(baseExecution.Workflow.Triggers) != len(execution.Workflow.Triggers) { - return errors.New(fmt.Sprintf("Bad length of trigger: %d (probably normal app)", len(execution.Workflow.Triggers))) - } - - if len(baseExecution.Results) >= len(execution.Results) { - return errors.New(fmt.Sprintf("Can't have less actions in a full execution than what exists: %d (old) vs %d (new)", len(baseExecution.Results), len(execution.Results))) - } - - //if baseExecution.Status != "WAITING" && baseExecution.Status != "EXECUTING" { - // return errors.New(fmt.Sprintf("Workflow is already finished or failed. Can't update")) - //} - - if execution.Status == "EXECUTING" { - //log.Printf("[INFO] Inside executing.") - extra := 0 - for _, trigger := range execution.Workflow.Triggers { - //log.Printf("Appname trigger (0): %s", trigger.AppName) - if trigger.AppName == "User Input" || trigger.AppName == "Shuffle Workflow" { - extra += 1 - } - } - - if len(execution.Workflow.Actions)+extra == len(execution.Results) { - execution.Status = "FINISHED" - } - - //log.Printf("[INFO] BASEEXECUTION LENGTH: %d", len(baseExecution.Workflow.Actions)+extra) - } - - // FIXME: Add extra here - //executionLength := len(baseExecution.Workflow.Actions) - //if executionLength != len(execution.Results) { - // return errors.New(fmt.Sprintf("Bad length of actions vs results: want: %d have: %d", executionLength, len(execution.Results))) - //} - - //log.Printf("\n\nSHOULD SET BACKEND DATA FOR EXEC \n\n") - err = shuffle.SetWorkflowExecution(ctx, execution, true) - if err == nil { - log.Printf("[INFO] Set workflowexecution based on new worker (>0.8.53) for execution %s. Actions: %d, Triggers: %d, Results: %d, Status: %s", execution.ExecutionId, len(execution.Workflow.Actions), len(execution.Workflow.Triggers), len(execution.Results), execution.Status) //, execution.Result) - //log.Printf("[INFO] Successfully set the execution to wait.") - } else { - log.Printf("[WARNING] Failed to set the execution to wait.") - } - - return nil -} - func handleWorkflowQueue(resp http.ResponseWriter, request *http.Request) { cors := handleCors(resp, request) if cors { return } + if request.Body == nil { + resp.WriteHeader(http.StatusBadRequest) + return + } + body, err := ioutil.ReadAll(request.Body) if err != nil { - log.Println("(3) Failed reading body for workflowqueue") + log.Println("[WARNING] (3) Failed reading body for workflowqueue") resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) return } //log.Printf("Actionresult unmarshal: %s", string(body)) - err = validateNewWorkerExecution(body) + log.Printf("[DEBUG] Got workflow result from %s of length %d", request.RemoteAddr, len(body)) + err = shuffle.ValidateNewWorkerExecution(body) if err == nil { resp.WriteHeader(200) - resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "Success"}`))) + resp.Write([]byte(fmt.Sprintf(`{"success": true, "reason": "success"}`))) return } else { //log.Printf("[WARNING] Handling other execution variant: %s", err) @@ -974,37 +912,15 @@ func runWorkflowExecutionTransaction(ctx context.Context, attempts int64, workfl //log.Printf("BASE LENGTH: %d", len(workflowExecution.Results)) workflowExecution, dbSave, err := shuffle.ParsedExecutionResult(ctx, *workflowExecution, actionResult, false) if err != nil { - log.Printf("[ERROR] Failed execution of parsedexecution: %s", err) + log.Printf("[ERROR] Failed running of parsedexecution: %s", err) resp.WriteHeader(401) resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "Failed getting execution"}`))) return } - //log.Printf("NEW LENGTH: %d", len(workflowExecution.Results)) - _ = dbSave //resultLength := len(workflowExecution.Results) setExecution := true - - //newExecution, err := shuffle.GetWorkflowExecution(ctx, workflowExecution.ExecutionId) - //if err == nil { - // //log.Printf("GOT GOOD EXECUTION CACHE FOR %s!", workflowExecution.ExecutionId) - // if len(newExecution.Results) > 0 && len(newExecution.Results) != resultLength { - // setExecution = false - // if attempts > 5 { - // //log.Printf("\n\nSkipping execution input - %d vs %d. Attempts: (%d)\n\n", len(parsedValue.Results), resultLength, attempts) - // } - - // attempts += 1 - // //if len(workflowExecution.Results) <= len(workflowExecution.Workflow.Actions) { - // // log.Printf("RUNNING AGAIN!!") - // // runWorkflowExecutionTransaction(ctx, attempts, workflowExecutionId, actionResult, resp) - // // return - // } - //} else { - // log.Printf("[WARNING] Failed getting cache for %s: %s", workflowExecution.ExecutionId, err) - //} - if setExecution || workflowExecution.Status == "FINISHED" || workflowExecution.Status == "ABORTED" || workflowExecution.Status == "FAILURE" { err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, true) //err = shuffle.SetWorkflowExecution(ctx, *workflowExecution, dbSave) @@ -1155,7 +1071,7 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) { if workflow.OrgId == user.ActiveOrg.Id && user.Role == "admin" { log.Printf("[INFO] User %s is deleting workflow %s as admin. Owner: %s", user.Username, workflow.ID, workflow.Owner) } else { - log.Printf("Wrong user (%s) for workflow %s", user.Username, workflow.ID) + log.Printf("[WARNING] Wrong user (%s) for workflow %s (delete workflow)", user.Username, workflow.ID) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return @@ -1175,9 +1091,14 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) { // log.Printf("Failed to delete webhook: %s", err) //} } else if item.TriggerType == "EMAIL" { - err = handleOutlookSubRemoval(ctx, user, workflow.ID, item.ID) + err = shuffle.HandleOutlookSubRemoval(ctx, user, workflow.ID, item.ID) if err != nil { - log.Printf("Failed to delete email sub: %s", err) + log.Printf("Failed to delete OUTLOOK email sub (checking gmail after): %s", err) + } + + err = shuffle.HandleGmailSubRemoval(ctx, user, workflow.ID, item.ID) + if err != nil { + log.Printf("Failed to delete gmail email sub: %s", err) } } @@ -1190,12 +1111,12 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) { // FIXME - maybe delete workflow executions err = shuffle.DeleteKey(ctx, "workflow", fileId) if err != nil { - log.Printf("Failed deleting key %s", fileId) + log.Printf("[DEBUG]] Failed deleting key %s", fileId) resp.WriteHeader(401) resp.Write([]byte(`{"success": false, "reason": "Failed deleting key"}`)) return } - log.Printf("[INFO] Should have deleted workflow %s", fileId) + log.Printf("[INFO] Should have deleted workflow %s (%s)", workflow.Name, fileId) //memcacheName := fmt.Sprintf("%s_%s", user.Username, fileId) //memcache.Delete(ctx, memcacheName) @@ -1204,6 +1125,7 @@ func deleteWorkflow(resp http.ResponseWriter, request *http.Request) { //cacheKey := fmt.Sprintf("%s_workflows", user.Id) cacheKey := fmt.Sprintf("%s_workflows", user.Id) shuffle.DeleteCache(ctx, cacheKey) + log.Printf("[DEBUG] Cleared workflow cache for %s (%s)", user.Username, user.Id) resp.WriteHeader(200) resp.Write([]byte(`{"success": true}`)) @@ -1346,6 +1268,22 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request } } + sourceAuth, sourceAuthOk := request.URL.Query()["source_auth"] + if sourceAuthOk { + //log.Printf("\n\n\nSETTING SOURCE WORKFLOW AUTH TO %s!!!\n\n\n", sourceAuth[0]) + workflowExecution.ExecutionSourceAuth = sourceAuth[0] + } else { + //log.Printf("Did NOT get source workflow") + } + + sourceNode, sourceNodeOk := request.URL.Query()["source_node"] + if sourceNodeOk { + //log.Printf("\n\n\nSETTING SOURCE WORKFLOW NODE TO %s!!!\n\n\n", sourceNode[0]) + workflowExecution.ExecutionSourceNode = sourceNode[0] + } else { + //log.Printf("Did NOT get source workflow") + } + //workflowExecution.ExecutionSource = "default" sourceWorkflow, sourceWorkflowOk := request.URL.Query()["source_workflow"] if sourceWorkflowOk { @@ -1353,7 +1291,6 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request workflowExecution.ExecutionSource = sourceWorkflow[0] } else { //log.Printf("Did NOT get source workflow") - } sourceExecution, sourceExecutionOk := request.URL.Query()["source_execution"] @@ -1370,7 +1307,7 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request //log.Println(body) //if string(body)[0] == "\"" && string(body)[string(body) - log.Printf("[INFO] Body: %s", string(body)) + log.Printf("[DEBUG] Body: %s", string(body)) } var execution shuffle.ExecutionRequest @@ -1551,23 +1488,31 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request } workflowExecution.ExecutionVariables = workflow.ExecutionVariables - // Local authorization for this single workflow used in workers. - - // FIXME: Used for cloud - //mappedData, err := json.Marshal(workflowExecution) - //if err != nil { - // log.Printf("Failed workflowexecution marshalling: %s", err) - // resp.WriteHeader(http.StatusInternalServerError) - // resp.Write([]byte(`{"success": false}`)) - // return - //} - - //log.Println(string(mappedData)) - if len(workflowExecution.Start) == 0 && len(workflowExecution.Workflow.Start) > 0 { workflowExecution.Start = workflowExecution.Workflow.Start } - //log.Printf("[INFO] New startnode: %s", workflowExecution.Start) + + startnodeFound := false + newStartnode := "" + for _, item := range workflowExecution.Workflow.Actions { + if item.ID == workflowExecution.Start { + startnodeFound = true + } + + if item.IsStartNode { + newStartnode = item.ID + } + } + + if !startnodeFound { + log.Printf("[INFO] Couldn't find startnode %s. Remapping to %#v", workflowExecution.Start, newStartnode) + + if len(newStartnode) > 0 { + workflowExecution.Start = newStartnode + } else { + return shuffle.WorkflowExecution{}, fmt.Sprintf("Startnode couldn't be found"), errors.New("Startnode isn't defined in this workflow..") + } + } childNodes := shuffle.FindChildNodes(workflowExecution, workflowExecution.Start) @@ -1611,20 +1556,71 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request return shuffle.WorkflowExecution{}, fmt.Sprintf("Auth ID %s doesn't exist", action.AuthenticationId), errors.New(fmt.Sprintf("Auth ID %s doesn't exist", action.AuthenticationId)) } - // Rebuild params with the right data. This is to prevent issues on the frontend - newParams := []shuffle.WorkflowAppActionParameter{} - for _, param := range action.Parameters { - - for _, authparam := range curAuth.Fields { - if param.Name == authparam.Key { - param.Value = authparam.Value - //log.Printf("Name: %s - value: %s", param.Name, param.Value) - //log.Printf("Name: %s - value: %s\n", param.Name, param.Value) + if curAuth.Encrypted { + setField := true + newFields := []shuffle.AuthenticationStore{} + for _, field := range curAuth.Fields { + parsedKey := fmt.Sprintf("%s_%d_%s_%s", curAuth.OrgId, curAuth.Created, curAuth.Label, field.Key) + newValue, err := shuffle.HandleKeyDecryption(field.Value, parsedKey) + if err != nil { + log.Printf("[WARNING] Failed decryption for %s: %s", field.Key, err) + setField = false break } + + field.Value = newValue + newFields = append(newFields, field) } - newParams = append(newParams, param) + if setField { + curAuth.Fields = newFields + } + } else { + log.Printf("[INFO] AUTH IS NOT ENCRYPTED - attempting encrypting!") + err = shuffle.SetWorkflowAppAuthDatastore(ctx, curAuth, curAuth.Id) + if err != nil { + log.Printf("[WARNING] Failed running encryption during execution: %s", err) + } + } + + newParams := []shuffle.WorkflowAppActionParameter{} + if strings.ToLower(curAuth.Type) == "oauth2" { + log.Printf("[DEBUG] Should replace auth parameters (Oauth2)") + + for _, param := range curAuth.Fields { + if param.Key == "expiration" { + continue + } + + newParams = append(newParams, shuffle.WorkflowAppActionParameter{ + Name: param.Key, + Value: param.Value, + }) + } + + for _, param := range action.Parameters { + //log.Printf("Param: %#v", param) + if param.Configuration { + continue + } + + newParams = append(newParams, param) + } + } else { + // Rebuild params with the right data. This is to prevent issues on the frontend + for _, param := range action.Parameters { + + for _, authparam := range curAuth.Fields { + if param.Name == authparam.Key { + param.Value = authparam.Value + //log.Printf("Name: %s - value: %s", param.Name, param.Value) + //log.Printf("Name: %s - value: %s\n", param.Name, param.Value) + break + } + } + + newParams = append(newParams, param) + } } action.Parameters = newParams @@ -1925,7 +1921,6 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request //b, err := json.Marshal(workflowExecution) //if err == nil { - // log.Printf("%s", string(b)) // log.Printf("LEN: %d", len(string(b))) // //workflowExecution.ExecutionOrg.SyncFeatures = Org{} //} @@ -1940,8 +1935,8 @@ func handleExecution(id string, workflow shuffle.Workflow, request *http.Request //Org []Org `json:"org,omitempty" datastore:"org"` err = shuffle.SetWorkflowExecution(ctx, workflowExecution, true) if err != nil { - log.Printf("Error saving workflow execution for updates %s: %s", topic, err) - return shuffle.WorkflowExecution{}, "Failed getting workflowexecution", err + log.Printf("[WARNING] Error saving workflow execution for updates %s: %s", topic, err) + return shuffle.WorkflowExecution{}, fmt.Sprintf("Failed setting workflowexecution: %s", err), err } // Adds queue for onprem execution @@ -2122,13 +2117,11 @@ func executeWorkflow(resp http.ResponseWriter, request *http.Request) { return } - // FIXME - have a check for org etc too.. - // FIXME - admin check like this? idk if user.Id != workflow.Owner && user.Role != "scheduler" && user.Role != fmt.Sprintf("workflow_%s", fileId) { if workflow.OrgId == user.ActiveOrg.Id && user.Role == "admin" { - log.Printf("[INFO] Letting user %s execute %s because they're admin of the same org", user.Username, workflow.ID) + log.Printf("[AUDIT] Letting user %s execute %s because they're admin of the same org", user.Username, workflow.ID) } else { - log.Printf("[WARNING] Wrong user (%s) for workflow %s (execute)", user.Username, workflow.ID) + log.Printf("[AUDIT] Wrong user (%s) for workflow %s (execute)", user.Username, workflow.ID) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return @@ -2201,9 +2194,9 @@ func stopSchedule(resp http.ResponseWriter, request *http.Request) { return } - if user.Id != workflow.Owner && user.Role != "scheduler" { + if user.Id != workflow.Owner || len(user.Id) == 0 { if workflow.OrgId == user.ActiveOrg.Id && user.Role == "admin" { - log.Printf("[DEBUG] User %s is accessing workflow %s as admin", user.Username, workflow.ID) + log.Printf("[AUDIT] User %s is accessing workflow %s as admin (stop schedule)", user.Username, workflow.ID) } else { log.Printf("[WARNING] Wrong user (%s) for workflow %s (stop schedule)", user.Username, workflow.ID) resp.WriteHeader(401) @@ -2347,7 +2340,7 @@ func stopScheduleGCP(resp http.ResponseWriter, request *http.Request) { // FIXME - have a check for org etc too.. // FIXME - admin check like this? idk if user.Id != workflow.Owner && user.Role != "scheduler" { - log.Printf("Wrong user (%s) for workflow %s (stop schedule)", user.Username, workflow.ID) + log.Printf("[WARNING] Wrong user (%s) for workflow %s (stop schedule)", user.Username, workflow.ID) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return @@ -2411,7 +2404,7 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) { user, err := shuffle.HandleApiAuthentication(resp, request) if err != nil { - log.Printf("Api authentication failed in schedule workflow: %s", err) + log.Printf("[WARNING] Api authentication failed in schedule workflow: %s", err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return @@ -2445,13 +2438,15 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) { return } - // FIXME - have a check for org etc too.. - // FIXME - admin check like this? idk - if user.Id != workflow.Owner && user.Role != "scheduler" { - log.Printf("Wrong user (%s) for workflow %s", user.Username, workflow.ID) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return + if user.Id != workflow.Owner || len(user.Id) == 0 { + if workflow.OrgId == user.ActiveOrg.Id && user.Role == "admin" { + log.Printf("[INFO] User %s is deleting workflow %s as admin. Owner: %s", user.Username, workflow.ID, workflow.Owner) + } else { + log.Printf("[WARNING] Wrong user (%s) for workflow %s (schedule start). Owner: %s", user.Username, workflow.ID, workflow.Owner) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } } if len(workflow.Actions) == 0 { @@ -2496,7 +2491,7 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) { startNode = workflow.Start } - log.Printf("Startnode: %s", startNode) + //log.Printf("Startnode: %s", startNode) if len(schedule.Id) != 36 { log.Printf("ID length is not 36 for schedule: %s", err) @@ -2597,7 +2592,7 @@ func scheduleWorkflow(resp http.ResponseWriter, request *http.Request) { } } - log.Printf("Schedulearg: %s", parsedBody) + //log.Printf("Schedulearg: %s", parsedBody) err = createSchedule( ctx, @@ -2651,25 +2646,18 @@ func getWorkflowApps(resp http.ResponseWriter, request *http.Request) { return } - // FIXME - set this to be per user IF logged in, - // as there might exist private and public - //memcacheName := "all_apps" - ctx := context.Background() - // Just need to be logged in - // FIXME - need to be logged in? user, userErr := shuffle.HandleApiAuthentication(resp, request) if userErr != nil { - log.Printf("Continuing with apps even without auth") - //log.Printf("Api authentication failed in get all apps: %s", userErr) - //resp.WriteHeader(401) - //resp.Write([]byte(`{"success": false}`)) - //return + log.Printf("[WARNING] Api authentication failed in get all apps - this does NOT require auth in cloud.: %s", userErr) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return } workflowapps, err := shuffle.GetAllWorkflowApps(ctx, 1000) if err != nil { - log.Printf("Failed getting apps (getworkflowapps): %s", err) + log.Printf("{WARNING] Failed getting apps (getworkflowapps): %s", err) resp.WriteHeader(401) resp.Write([]byte(`{"success": false}`)) return @@ -2706,29 +2694,6 @@ func getWorkflowApps(resp http.ResponseWriter, request *http.Request) { resp.Write(newbody) } -// Bad check for workflowapps :) -// FIXME - use tags and struct reflection -func checkWorkflowApp(workflowApp shuffle.WorkflowApp) error { - // Validate fields - if workflowApp.Name == "" { - return errors.New("App field name doesn't exist") - } - - if workflowApp.Description == "" { - return errors.New("App field description doesn't exist") - } - - if workflowApp.AppVersion == "" { - return errors.New("App field app_version doesn't exist") - } - - if workflowApp.ContactInfo.Name == "" { - return errors.New("App field contact_info.name doesn't exist") - } - - return nil -} - func handleGetfile(resp http.ResponseWriter, request *http.Request) ([]byte, error) { // Upload file here first request.ParseMultipartForm(32 << 20) @@ -3055,141 +3020,6 @@ func handleAppHotloadRequest(resp http.ResponseWriter, request *http.Request) { resp.WriteHeader(200) resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) } - -func loadSpecificApps(resp http.ResponseWriter, request *http.Request) { - cors := handleCors(resp, request) - if cors { - return - } - - // Just need to be logged in - // FIXME - should have some permissions? - _, err := shuffle.HandleApiAuthentication(resp, request) - if err != nil { - log.Printf("[WARNING] Api authentication failed in load specific apps: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - body, err := ioutil.ReadAll(request.Body) - if err != nil { - log.Printf("Error with body read: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - // Field1 & 2 can be a lot of things. - // Field1 = Username - // Field2 = Password - type tmpStruct struct { - URL string `json:"url"` - Branch string `json:"branch"` - Field1 string `json:"field_1"` - Field2 string `json:"field_2"` - ForceUpdate bool `json:"force_update"` - } - //log.Printf("Body: %s", string(body)) - - var tmpBody tmpStruct - err = json.Unmarshal(body, &tmpBody) - if err != nil { - log.Printf("Error with unmarshal tmpBody: %s", err) - resp.WriteHeader(401) - resp.Write([]byte(`{"success": false}`)) - return - } - - fs := memfs.New() - - if strings.Contains(tmpBody.URL, "github") || strings.Contains(tmpBody.URL, "gitlab") || strings.Contains(tmpBody.URL, "bitbucket") { - cloneOptions := &git.CloneOptions{ - URL: tmpBody.URL, - } - - if len(tmpBody.Branch) > 0 && tmpBody.Branch != "master" && tmpBody.Branch != "main" { - cloneOptions.ReferenceName = plumbing.ReferenceName(tmpBody.Branch) - } - - // FIXME: Better auth. - if len(tmpBody.Field1) > 0 && len(tmpBody.Field2) > 0 { - cloneOptions.Auth = &http2.BasicAuth{ - Username: tmpBody.Field1, - Password: tmpBody.Field2, - } - } - - storer := memory.NewStorage() - r, err := git.Clone(storer, fs, cloneOptions) - if err != nil { - log.Printf("Failed loading repo %s into memory (github workflows 2): %s", tmpBody.URL, err) - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) - return - } - - dir, err := fs.ReadDir("/") - if err != nil { - log.Printf("FAiled reading folder: %s", err) - } - _ = r - - if tmpBody.ForceUpdate { - log.Printf("[INFO] Running with force update!") - } else { - log.Printf("[INFO] Updating apps with updates (no force)") - } - - if tmpBody.ForceUpdate { - ctx := context.Background() - dockercli, err := client.NewEnvClient() - if err == nil { - _, err := dockercli.ImagePull(ctx, "frikky/shuffle:app_sdk", types.ImagePullOptions{}) - if err != nil { - log.Printf("[WARNING] Failed to download apps with the new App SDK: %s", err) - } - } else { - log.Printf("[WARNING] Failed to download apps with the new App SDK because of docker cli: %s", err) - } - } - - iterateAppGithubFolders(fs, dir, "", "", tmpBody.ForceUpdate) - - } else if strings.Contains(tmpBody.URL, "s3") { - //https://docs.aws.amazon.com/sdk-for-go/api/service/s3/ - - //sess := session.Must(session.NewSession()) - //downloader := s3manager.NewDownloader(sess) - - //// Write the contents of S3 Object to the file - //storer := memory.NewStorage() - //n, err := downloader.Download(storer, &s3.GetObjectInput{ - // Bucket: aws.String(myBucket), - // Key: aws.String(myString), - //}) - //if err != nil { - // return fmt.Errorf("failed to download file, %v", err) - //} - //fmt.Printf("file downloaded, %d bytes\n", n) - } else { - resp.WriteHeader(401) - resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s is unsupported"}`, tmpBody.URL))) - return - } - - ctx := context.Background() - cacheKey := fmt.Sprintf("workflowapps-sorted-100") - shuffle.DeleteCache(ctx, cacheKey) - cacheKey = fmt.Sprintf("workflowapps-sorted-500") - shuffle.DeleteCache(ctx, cacheKey) - cacheKey = fmt.Sprintf("workflowapps-sorted-1000") - shuffle.DeleteCache(ctx, cacheKey) - - resp.WriteHeader(200) - resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) -} - func iterateOpenApiGithub(fs billy.Filesystem, dir []os.FileInfo, extra string, onlyname string) error { ctx := context.Background() @@ -3268,7 +3098,6 @@ func iterateOpenApiGithub(fs billy.Filesystem, dir []os.FileInfo, extra string, // 3. Load this as a "standby" app // FIXME: This should be a function ROFL - //log.Printf("%s", string(readFile)) swagger, err := openapi3.NewSwaggerLoader().LoadSwaggerFromData([]byte(parsedOpenApi.Body)) if err != nil { log.Printf("[WARNING] Swagger validation error in loop (%s): %s. Continuing.", filename, err) @@ -3282,7 +3111,7 @@ func iterateOpenApiGithub(fs billy.Filesystem, dir []os.FileInfo, extra string, //log.Printf("Should generate yaml") swagger, api, _, err := shuffle.GenerateYaml(swagger, parsedOpenApi.ID) if err != nil { - log.Printf("Failed building and generating yaml in loop (2) (%s): %s. Continuing.", filename, err) + log.Printf("[WARNING] Failed building and generating yaml in loop (2) (%s): %s. Continuing.", filename, err) continue } @@ -3453,341 +3282,6 @@ func iterateWorkflowGithubFolders(fs billy.Filesystem, dir []os.FileInfo, extra return err } -type buildLaterStruct struct { - Tags []string - Extra string - Id string -} - -// Onlyname is used to -func iterateAppGithubFolders(fs billy.Filesystem, dir []os.FileInfo, extra string, onlyname string, forceUpdate bool) ([]buildLaterStruct, []buildLaterStruct, error) { - var err error - - allapps := []shuffle.WorkflowApp{} - - // These are slow apps to build with some funky mechanisms - reservedNames := []string{ - "OWA", - "NLP", - "YARA", - } - - // It's here to prevent getting them in every iteration - buildLaterFirst := []buildLaterStruct{} - buildLaterList := []buildLaterStruct{} - ctx := context.Background() - for _, file := range dir { - if len(onlyname) > 0 && file.Name() != onlyname { - continue - } - - // Folder? - switch mode := file.Mode(); { - case mode.IsDir(): - tmpExtra := fmt.Sprintf("%s%s/", extra, file.Name()) - dir, err := fs.ReadDir(tmpExtra) - if err != nil { - log.Printf("Failed to read dir: %s", err) - continue - } - - // Go routine? Hmm, this can be super quick I guess - buildFirst, buildLast, err := iterateAppGithubFolders(fs, dir, tmpExtra, "", forceUpdate) - - for _, item := range buildFirst { - buildLaterFirst = append(buildLaterFirst, item) - } - - for _, item := range buildLast { - buildLaterList = append(buildLaterList, item) - } - - if err != nil { - log.Printf("[WARNING] Error reading folder: %s", err) - //buildFirst, buildLast, err := iterateAppGithubFolders(fs, dir, tmpExtra, "", forceUpdate) - - if !forceUpdate { - return buildLaterFirst, buildLaterList, err - } - } - - case mode.IsRegular(): - // Check the file - filename := file.Name() - if filename == "Dockerfile" { - // Set up to make md5 and check if the app is new (api.yaml+src/app.py+Dockerfile) - // Check if Dockerfile, app.py or api.yaml has changed. Hash? - //log.Printf("Handle Dockerfile in location %s", extra) - // Try api.yaml and api.yml - fullPath := fmt.Sprintf("%s%s", extra, "api.yaml") - fileReader, err := fs.Open(fullPath) - if err != nil { - fullPath = fmt.Sprintf("%s%s", extra, "api.yml") - fileReader, err = fs.Open(fullPath) - if err != nil { - log.Printf("Failed finding api.yaml/yml: %s", err) - continue - } - } - - //log.Printf("HANDLING DOCKER FILEREADER - SEARCH&REPLACE?") - - appfileData, err := ioutil.ReadAll(fileReader) - if err != nil { - log.Printf("Failed reading %s: %s", fullPath, err) - continue - } - - if len(appfileData) == 0 { - log.Printf("Failed reading %s - length is 0.", fullPath) - continue - } - - // func md5sum(data []byte) string { - // Make hash - appPython := fmt.Sprintf("%s/src/app.py", extra) - appPythonReader, err := fs.Open(appPython) - if err != nil { - log.Printf("Failed to read python app %s", appPython) - continue - } - - appPythonData, err := ioutil.ReadAll(appPythonReader) - if err != nil { - log.Printf("Failed reading appdata %s: %s", appPython, err) - continue - } - - dockerFp := fmt.Sprintf("%s/Dockerfile", extra) - dockerfile, err := fs.Open(dockerFp) - if err != nil { - log.Printf("Failed to read dockerfil %s", appPython) - continue - } - - dockerfileData, err := ioutil.ReadAll(dockerfile) - if err != nil { - log.Printf("Failed to read dockerfile") - continue - } - - combined := []byte{} - combined = append(combined, appfileData...) - combined = append(combined, appPythonData...) - combined = append(combined, dockerfileData...) - md5 := md5sum(combined) - - var workflowapp shuffle.WorkflowApp - err = gyaml.Unmarshal(appfileData, &workflowapp) - if err != nil { - log.Printf("[WARNING] Failed building workflowapp %s: %s", extra, err) - return buildLaterFirst, buildLaterList, errors.New(fmt.Sprintf("Failed building %s: %s", extra, err)) - //continue - } - - newName := workflowapp.Name - newName = strings.ReplaceAll(newName, " ", "-") - - tags := []string{ - fmt.Sprintf("%s:%s_%s", baseDockerName, strings.ToLower(newName), workflowapp.AppVersion), - } - - if len(allapps) == 0 { - allapps, err = shuffle.GetAllWorkflowApps(ctx, 0) - if err != nil { - log.Printf("[WARNING] Failed getting apps to verify: %s", err) - continue - } - } - - // Make an option to override existing apps? - //Hash string `json:"hash" datastore:"hash" yaml:"hash"` // api.yaml+dockerfile+src/app.py for apps - removeApps := []string{} - skip := false - for _, app := range allapps { - if app.Name == workflowapp.Name && app.AppVersion == workflowapp.AppVersion { - // FIXME: Check if there's a new APP_SDK as well. - // Skip this check if app_sdk is new. - if app.Hash == md5 && app.Hash != "" && !forceUpdate { - skip = true - break - } - - //log.Printf("Overriding app %s:%s as it exists but has different hash.", app.Name, app.AppVersion) - removeApps = append(removeApps, app.ID) - } - } - - if skip && !forceUpdate { - continue - } - - // Fixes (appends) authentication parameters if they're required - if workflowapp.Authentication.Required { - //log.Printf("[INFO] Checking authentication fields and appending for %s!", workflowapp.Name) - // FIXME: - // Might require reflection into the python code to append the fields as well - for index, action := range workflowapp.Actions { - if action.AuthNotRequired { - log.Printf("Skipping auth setup: %s", action.Name) - continue - } - - // 1. Check if authentication params exists at all - // 2. Check if they're present in the action - // 3. Add them IF they DONT exist - // 4. Fix python code with reflection (FIXME) - appendParams := []shuffle.WorkflowAppActionParameter{} - for _, fieldname := range workflowapp.Authentication.Parameters { - found := false - for index, param := range action.Parameters { - if param.Name == fieldname.Name { - found = true - - action.Parameters[index].Configuration = true - //log.Printf("Set config to true for field %s!", param.Name) - break - } - } - - if !found { - appendParams = append(appendParams, shuffle.WorkflowAppActionParameter{ - Name: fieldname.Name, - Description: fieldname.Description, - Example: fieldname.Example, - Required: fieldname.Required, - Configuration: true, - Schema: fieldname.Schema, - }) - } - } - - if len(appendParams) > 0 { - //log.Printf("[AUTH] Appending %d params to the START of %s", len(appendParams), action.Name) - workflowapp.Actions[index].Parameters = append(appendParams, workflowapp.Actions[index].Parameters...) - } - - } - } - - err = checkWorkflowApp(workflowapp) - if err != nil { - log.Printf("[DEBUG] %s for app %s:%s", err, workflowapp.Name, workflowapp.AppVersion) - continue - } - - if len(removeApps) > 0 { - for _, item := range removeApps { - log.Printf("[WARNING] Removing duplicate app: %s", item) - err = shuffle.DeleteKey(ctx, "workflowapp", item) - if err != nil { - log.Printf("[ERROR] Failed deleting duplicate %s: %s", item, err) - } - } - } - - workflowapp.ID = uuid.NewV4().String() - workflowapp.IsValid = true - workflowapp.Verified = true - workflowapp.Sharing = true - workflowapp.Downloaded = true - workflowapp.Hash = md5 - workflowapp.Public = true - - err = shuffle.SetWorkflowAppDatastore(ctx, workflowapp, workflowapp.ID) - if err != nil { - log.Printf("[WARNING] Failed setting workflowapp in intro: %s", err) - continue - } - - /* - err = increaseStatisticsField(ctx, "total_apps_created", workflowapp.ID, 1, "") - if err != nil { - log.Printf("Failed to increase total apps created stats: %s", err) - } - - err = increaseStatisticsField(ctx, "total_apps_loaded", workflowapp.ID, 1, "") - if err != nil { - log.Printf("Failed to increase total apps loaded stats: %s", err) - } - */ - - //log.Printf("Added %s:%s to the database", workflowapp.Name, workflowapp.AppVersion) - - // ID can be used to e.g. set a build status. - buildLater := buildLaterStruct{ - Tags: tags, - Extra: extra, - Id: workflowapp.ID, - } - - reservedFound := false - for _, appname := range reservedNames { - if strings.ToUpper(workflowapp.Name) == strings.ToUpper(appname) { - buildLaterList = append(buildLaterList, buildLater) - - reservedFound = true - break - } - } - - /// Only upload if successful and no errors - if !reservedFound { - buildLaterFirst = append(buildLaterFirst, buildLater) - } else { - log.Printf("[WARNING] Skipping build of %s to later", workflowapp.Name) - } - } - } - } - - if len(buildLaterFirst) == 0 && len(buildLaterList) == 0 { - return buildLaterFirst, buildLaterList, err - } - - // This is getting silly - cacheKey := fmt.Sprintf("workflowapps-sorted-100") - shuffle.DeleteCache(ctx, cacheKey) - cacheKey = fmt.Sprintf("workflowapps-sorted-500") - shuffle.DeleteCache(ctx, cacheKey) - cacheKey = fmt.Sprintf("workflowapps-sorted-1000") - shuffle.DeleteCache(ctx, cacheKey) - - //log.Printf("BUILDLATERFIRST: %d, BUILDLATERLIST: %d", len(buildLaterFirst), len(buildLaterList)) - if len(extra) == 0 { - log.Printf("[INFO] Starting build of %d containers (FIRST)", len(buildLaterFirst)) - for _, item := range buildLaterFirst { - err = buildImageMemory(fs, item.Tags, item.Extra, true) - if err != nil { - log.Printf("Failed image build memory: %s", err) - } else { - if len(item.Tags) > 0 { - log.Printf("[INFO] Successfully built image %s", item.Tags[0]) - - } else { - log.Printf("[INFO] Successfully built Docker image") - } - } - } - - log.Printf("[INFO] Starting build of %d skipped docker images", len(buildLaterList)) - for _, item := range buildLaterList { - err = buildImageMemory(fs, item.Tags, item.Extra, true) - if err != nil { - log.Printf("[INFO] Failed image build memory: %s", err) - } else { - if len(item.Tags) > 0 { - log.Printf("[INFO] Successfully built image %s", item.Tags[0]) - } else { - log.Printf("[INFO] Successfully built Docker image") - } - } - } - } - - return buildLaterFirst, buildLaterList, err -} - func setNewWorkflowApp(resp http.ResponseWriter, request *http.Request) { cors := handleCors(resp, request) if cors { @@ -3846,7 +3340,7 @@ func setNewWorkflowApp(resp http.ResponseWriter, request *http.Request) { return } - err = checkWorkflowApp(workflowapp) + err = shuffle.CheckWorkflowApp(workflowapp) if err != nil { log.Printf("%s for app %s:%s", err, workflowapp.Name, workflowapp.AppVersion) resp.WriteHeader(401) @@ -4093,3 +3587,532 @@ func executeSingleAction(resp http.ResponseWriter, request *http.Request) { resp.WriteHeader(200) resp.Write(returnBytes) } + +// Onlyname is used to +func IterateAppGithubFolders(ctx context.Context, fs billy.Filesystem, dir []os.FileInfo, extra string, onlyname string, forceUpdate bool) ([]shuffle.BuildLaterStruct, []shuffle.BuildLaterStruct, error) { + var err error + + allapps := []shuffle.WorkflowApp{} + + // These are slow apps to build with some funky mechanisms + reservedNames := []string{ + "OWA", + "NLP", + "YARA", + "ATTACK-PREDICTOR", + } + //if strings.ToUpper(workflowapp.Name) == strings.ToUpper(appname) { + + // It's here to prevent getting them in every iteration + buildLaterFirst := []shuffle.BuildLaterStruct{} + buildLaterList := []shuffle.BuildLaterStruct{} + for _, file := range dir { + if len(onlyname) > 0 && file.Name() != onlyname { + continue + } + + // Folder? + switch mode := file.Mode(); { + case mode.IsDir(): + tmpExtra := fmt.Sprintf("%s%s/", extra, file.Name()) + dir, err := fs.ReadDir(tmpExtra) + if err != nil { + log.Printf("Failed to read dir: %s", err) + continue + } + + // Go routine? Hmm, this can be super quick I guess + buildFirst, buildLast, err := IterateAppGithubFolders(ctx, fs, dir, tmpExtra, "", forceUpdate) + + for _, item := range buildFirst { + buildLaterFirst = append(buildLaterFirst, item) + } + + for _, item := range buildLast { + buildLaterList = append(buildLaterList, item) + } + + if err != nil { + log.Printf("[WARNING] Error reading folder: %s", err) + //buildFirst, buildLast, err := IterateAppGithubFolders(fs, dir, tmpExtra, "", forceUpdate) + + if !forceUpdate { + return buildLaterFirst, buildLaterList, err + } + } + + case mode.IsRegular(): + // Check the file + filename := file.Name() + if filename == "Dockerfile" { + // Set up to make md5 and check if the app is new (api.yaml+src/app.py+Dockerfile) + // Check if Dockerfile, app.py or api.yaml has changed. Hash? + //log.Printf("Handle Dockerfile in location %s", extra) + // Try api.yaml and api.yml + fullPath := fmt.Sprintf("%s%s", extra, "api.yaml") + fileReader, err := fs.Open(fullPath) + if err != nil { + fullPath = fmt.Sprintf("%s%s", extra, "api.yml") + fileReader, err = fs.Open(fullPath) + if err != nil { + log.Printf("Failed finding api.yaml/yml: %s", err) + continue + } + } + + //log.Printf("HANDLING DOCKER FILEREADER - SEARCH&REPLACE?") + + appfileData, err := ioutil.ReadAll(fileReader) + if err != nil { + log.Printf("Failed reading %s: %s", fullPath, err) + continue + } + + if len(appfileData) == 0 { + log.Printf("Failed reading %s - length is 0.", fullPath) + continue + } + + // func md5sum(data []byte) string { + // Make hash + appPython := fmt.Sprintf("%s/src/app.py", extra) + appPythonReader, err := fs.Open(appPython) + if err != nil { + log.Printf("Failed to read python app %s", appPython) + continue + } + + appPythonData, err := ioutil.ReadAll(appPythonReader) + if err != nil { + log.Printf("Failed reading appdata %s: %s", appPython, err) + continue + } + + dockerFp := fmt.Sprintf("%s/Dockerfile", extra) + dockerfile, err := fs.Open(dockerFp) + if err != nil { + log.Printf("Failed to read dockerfil %s", appPython) + continue + } + + dockerfileData, err := ioutil.ReadAll(dockerfile) + if err != nil { + log.Printf("Failed to read dockerfile") + continue + } + + combined := []byte{} + combined = append(combined, appfileData...) + combined = append(combined, appPythonData...) + combined = append(combined, dockerfileData...) + md5 := md5sum(combined) + + var workflowapp shuffle.WorkflowApp + err = gyaml.Unmarshal(appfileData, &workflowapp) + if err != nil { + log.Printf("[WARNING] Failed building workflowapp %s: %s", extra, err) + return buildLaterFirst, buildLaterList, errors.New(fmt.Sprintf("Failed building %s: %s", extra, err)) + //continue + } + + newName := workflowapp.Name + newName = strings.ReplaceAll(newName, " ", "-") + + readmeNames := []string{"README.md", "README", "readme", "readme.md", "README.MD"} + for _, readmeName := range readmeNames { + readmePath := fmt.Sprintf("%s%s", extra, readmeName) + readmeInfo, err := fs.Open(readmePath) + if err != nil { + //log.Printf("[WARNING] Failed to read README path %s", readmePath) + continue + } + + fileData, err := ioutil.ReadAll(readmeInfo) + if err != nil { + log.Printf("[WARNING] Failed to read readme file at %s", readmePath) + continue + } else { + workflowapp.Documentation = string(fileData) + //log.Printf("[INFO] Found %s (README) file of length %d for %s:%s", readmePath, len(workflowapp.Documentation), newName, workflowapp.AppVersion) + break + } + } + + if len(workflowapp.Documentation) == 0 { + for _, readmeName := range readmeNames { + readmePath := fmt.Sprintf("%s../%s", extra, readmeName) + readmeInfo, err := fs.Open(readmePath) + if err != nil { + //log.Printf("[WARNING] Failed to read README path %s", readmePath) + continue + } + + fileData, err := ioutil.ReadAll(readmeInfo) + if err != nil { + log.Printf("[WARNING] Failed to read readme file at %s", readmePath) + continue + } else { + workflowapp.Documentation = string(fileData) + //log.Printf("[INFO] Found %s (README) file of length %d for %s:%s", readmePath, len(workflowapp.Documentation), newName, workflowapp.AppVersion) + break + } + } + } + + workflowapp.ReferenceInfo.GithubUrl = fmt.Sprintf("https://github.com/frikky/shuffle-apps/tree/master/%s/%s", strings.ToLower(newName), workflowapp.AppVersion) + + tags := []string{ + fmt.Sprintf("%s:%s_%s", baseDockerName, strings.ToLower(newName), workflowapp.AppVersion), + } + + if len(allapps) == 0 { + allapps, err = shuffle.GetAllWorkflowApps(ctx, 0) + if err != nil { + log.Printf("[WARNING] Failed getting apps to verify: %s", err) + continue + } + } + + // Make an option to override existing apps? + //Hash string `json:"hash" datastore:"hash" yaml:"hash"` // api.yaml+dockerfile+src/app.py for apps + removeApps := []string{} + skip := false + for _, app := range allapps { + if app.Name == workflowapp.Name && app.AppVersion == workflowapp.AppVersion { + // FIXME: Check if there's a new APP_SDK as well. + // Skip this check if app_sdk is new. + if app.Hash == md5 && app.Hash != "" && !forceUpdate { + skip = true + break + } + + //log.Printf("Overriding app %s:%s as it exists but has different hash.", app.Name, app.AppVersion) + removeApps = append(removeApps, app.ID) + } + } + + if skip && !forceUpdate { + continue + } + + // Fixes (appends) authentication parameters if they're required + if workflowapp.Authentication.Required { + //log.Printf("[INFO] Checking authentication fields and appending for %s!", workflowapp.Name) + // FIXME: + // Might require reflection into the python code to append the fields as well + for index, action := range workflowapp.Actions { + if action.AuthNotRequired { + log.Printf("Skipping auth setup: %s", action.Name) + continue + } + + // 1. Check if authentication params exists at all + // 2. Check if they're present in the action + // 3. Add them IF they DONT exist + // 4. Fix python code with reflection (FIXME) + appendParams := []shuffle.WorkflowAppActionParameter{} + for _, fieldname := range workflowapp.Authentication.Parameters { + found := false + for index, param := range action.Parameters { + if param.Name == fieldname.Name { + found = true + + action.Parameters[index].Configuration = true + //log.Printf("Set config to true for field %s!", param.Name) + break + } + } + + if !found { + appendParams = append(appendParams, shuffle.WorkflowAppActionParameter{ + Name: fieldname.Name, + Description: fieldname.Description, + Example: fieldname.Example, + Required: fieldname.Required, + Configuration: true, + Schema: fieldname.Schema, + }) + } + } + + if len(appendParams) > 0 { + //log.Printf("[AUTH] Appending %d params to the START of %s", len(appendParams), action.Name) + workflowapp.Actions[index].Parameters = append(appendParams, workflowapp.Actions[index].Parameters...) + } + + } + } + + err = checkWorkflowApp(workflowapp) + if err != nil { + log.Printf("[DEBUG] %s for app %s:%s", err, workflowapp.Name, workflowapp.AppVersion) + continue + } + + if len(removeApps) > 0 { + for _, item := range removeApps { + log.Printf("[WARNING] Removing duplicate app: %s", item) + err = shuffle.DeleteKey(ctx, "workflowapp", item) + if err != nil { + log.Printf("[ERROR] Failed deleting duplicate %s: %s", item, err) + } + } + } + + workflowapp.ID = uuid.NewV4().String() + workflowapp.IsValid = true + workflowapp.Verified = true + workflowapp.Sharing = true + workflowapp.Downloaded = true + workflowapp.Hash = md5 + workflowapp.Public = true + + err = shuffle.SetWorkflowAppDatastore(ctx, workflowapp, workflowapp.ID) + if err != nil { + log.Printf("[WARNING] Failed setting workflowapp in intro: %s", err) + continue + } + + /* + err = increaseStatisticsField(ctx, "total_apps_created", workflowapp.ID, 1, "") + if err != nil { + log.Printf("Failed to increase total apps created stats: %s", err) + } + + err = increaseStatisticsField(ctx, "total_apps_loaded", workflowapp.ID, 1, "") + if err != nil { + log.Printf("Failed to increase total apps loaded stats: %s", err) + } + */ + + //log.Printf("Added %s:%s to the database", workflowapp.Name, workflowapp.AppVersion) + + // ID can be used to e.g. set a build status. + buildLater := shuffle.BuildLaterStruct{ + Tags: tags, + Extra: extra, + Id: workflowapp.ID, + } + + reservedFound := false + for _, appname := range reservedNames { + if strings.ToUpper(workflowapp.Name) == strings.ToUpper(appname) { + buildLaterList = append(buildLaterList, buildLater) + + reservedFound = true + break + } + } + + /// Only upload if successful and no errors + if !reservedFound { + buildLaterFirst = append(buildLaterFirst, buildLater) + } else { + log.Printf("[WARNING] Skipping build of %s to later", workflowapp.Name) + } + } + } + } + + if len(buildLaterFirst) == 0 && len(buildLaterList) == 0 { + return buildLaterFirst, buildLaterList, err + } + + // This is getting silly + cacheKey := fmt.Sprintf("workflowapps-sorted-100") + shuffle.DeleteCache(ctx, cacheKey) + cacheKey = fmt.Sprintf("workflowapps-sorted-500") + shuffle.DeleteCache(ctx, cacheKey) + cacheKey = fmt.Sprintf("workflowapps-sorted-1000") + shuffle.DeleteCache(ctx, cacheKey) + + if len(extra) == 0 { + log.Printf("[INFO] Starting build of %d containers (FIRST)", len(buildLaterFirst)) + for _, item := range buildLaterFirst { + err = buildImageMemory(fs, item.Tags, item.Extra, true) + if err != nil { + log.Printf("Failed image build memory: %s", err) + } else { + if len(item.Tags) > 0 { + log.Printf("[INFO] Successfully built image %s", item.Tags[0]) + + } else { + log.Printf("[INFO] Successfully built Docker image") + } + } + } + + if len(buildLaterList) > 0 { + log.Printf("[INFO] Starting build of %d skipped docker images", len(buildLaterList)) + for _, item := range buildLaterList { + err = buildImageMemory(fs, item.Tags, item.Extra, true) + if err != nil { + log.Printf("[INFO] Failed image build memory: %s", err) + } else { + if len(item.Tags) > 0 { + log.Printf("[INFO] Successfully built image %s", item.Tags[0]) + } else { + log.Printf("[INFO] Successfully built Docker image") + } + } + } + } + } + + return buildLaterFirst, buildLaterList, err +} + +func LoadSpecificApps(resp http.ResponseWriter, request *http.Request) { + cors := shuffle.HandleCors(resp, request) + if cors { + return + } + + // Just need to be logged in + user, err := shuffle.HandleApiAuthentication(resp, request) + if err != nil { + log.Printf("[WARNING] Api authentication failed in load specific apps: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + body, err := ioutil.ReadAll(request.Body) + if err != nil { + log.Printf("Error with body read: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + // Field1 & 2 can be a lot of things. + // Field1 = Username + // Field2 = Password + type tmpStruct struct { + URL string `json:"url"` + Branch string `json:"branch"` + Field1 string `json:"field_1"` + Field2 string `json:"field_2"` + ForceUpdate bool `json:"force_update"` + } + //log.Printf("Body: %s", string(body)) + + var tmpBody tmpStruct + err = json.Unmarshal(body, &tmpBody) + if err != nil { + log.Printf("Error with unmarshal tmpBody: %s", err) + resp.WriteHeader(401) + resp.Write([]byte(`{"success": false}`)) + return + } + + fs := memfs.New() + ctx := context.Background() + if strings.Contains(tmpBody.URL, "github") || strings.Contains(tmpBody.URL, "gitlab") || strings.Contains(tmpBody.URL, "bitbucket") { + cloneOptions := &git.CloneOptions{ + URL: tmpBody.URL, + } + + if len(tmpBody.Branch) > 0 && tmpBody.Branch != "master" && tmpBody.Branch != "main" { + cloneOptions.ReferenceName = plumbing.ReferenceName(tmpBody.Branch) + } + + // FIXME: Better auth. + if len(tmpBody.Field1) > 0 && len(tmpBody.Field2) > 0 { + cloneOptions.Auth = &http2.BasicAuth{ + Username: tmpBody.Field1, + Password: tmpBody.Field2, + } + } + + storer := memory.NewStorage() + r, err := git.Clone(storer, fs, cloneOptions) + if err != nil { + log.Printf("Failed loading repo %s into memory (github workflows 2): %s", tmpBody.URL, err) + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s"}`, err))) + return + } + + dir, err := fs.ReadDir("/") + if err != nil { + log.Printf("FAiled reading folder: %s", err) + } + _ = r + + if tmpBody.ForceUpdate { + log.Printf("[AUDIT] Running with force update from user %s (%s) for %s!", user.Username, user.Id, tmpBody.URL) + } else { + log.Printf("[AUDIT] Updating apps with updates for user %s (%s) for %s (no force)", user.Username, user.Id, tmpBody.URL) + } + + // As it's not even Docker + if tmpBody.ForceUpdate { + dockercli, err := dockerclient.NewEnvClient() + if err == nil { + _, err := dockercli.ImagePull(ctx, "frikky/shuffle:app_sdk", types.ImagePullOptions{}) + if err != nil { + log.Printf("[WARNING] Failed to download apps with the new App SDK: %s", err) + } + } else { + log.Printf("[WARNING] Failed to download apps with the new App SDK because of docker cli: %s", err) + } + } + + IterateAppGithubFolders(ctx, fs, dir, "", "", tmpBody.ForceUpdate) + + } else if strings.Contains(tmpBody.URL, "s3") { + //https://docs.aws.amazon.com/sdk-for-go/api/service/s3/ + + //sess := session.Must(session.NewSession()) + //downloader := s3manager.NewDownloader(sess) + + //// Write the contents of S3 Object to the file + //storer := memory.NewStorage() + //n, err := downloader.Download(storer, &s3.GetObjectInput{ + // Bucket: aws.String(myBucket), + // Key: aws.String(myString), + //}) + //if err != nil { + // return fmt.Errorf("failed to download file, %v", err) + //} + //fmt.Printf("file downloaded, %d bytes\n", n) + } else { + resp.WriteHeader(401) + resp.Write([]byte(fmt.Sprintf(`{"success": false, "reason": "%s is unsupported"}`, tmpBody.URL))) + return + } + + cacheKey := fmt.Sprintf("workflowapps-sorted-100") + shuffle.DeleteCache(ctx, cacheKey) + cacheKey = fmt.Sprintf("workflowapps-sorted-500") + shuffle.DeleteCache(ctx, cacheKey) + cacheKey = fmt.Sprintf("workflowapps-sorted-1000") + shuffle.DeleteCache(ctx, cacheKey) + + resp.WriteHeader(200) + resp.Write([]byte(fmt.Sprintf(`{"success": true}`))) +} + +// Bad check for workflowapps :) +// FIXME - use tags and struct reflection +func checkWorkflowApp(workflowApp shuffle.WorkflowApp) error { + // Validate fields + if workflowApp.Name == "" { + return errors.New("App field name doesn't exist") + } + + if workflowApp.Description == "" { + return errors.New("App field description doesn't exist") + } + + if workflowApp.AppVersion == "" { + return errors.New("App field app_version doesn't exist") + } + + if workflowApp.ContactInfo.Name == "" { + return errors.New("App field contact_info.name doesn't exist") + } + + return nil +} diff --git a/backend/tests/files.sh b/backend/tests/files.sh index d27a4755..64417941 100755 --- a/backend/tests/files.sh +++ b/backend/tests/files.sh @@ -1,16 +1,22 @@ -curl http://192.168.3.6:5001/api/v1/files/create -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" -d '{"filename": "file.txt", "org_id": "b199646b-16d2-456d-9fd6-b9972e929466", "workflow_id": "global"}' - -curl http://localhost:5001/api/v1/files/create -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" -d '{"filename": "file.txt", "org_id": "b199646b-16d2-456d-9fd6-b9972e929466", "workflow_id": "global"}' - -echo -curl http://localhost:5001/api/v1/files/e19cffe4-e2da-47e9-809e-904f5cb03687/upload -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" -F 'shuffle_file=@files.sh' - -curl http://localhost:5001/api/v1/files/1915981b-b897-4db1-8a2e-44bc34cead3b/content -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" -curl http://localhost:5001/api/v1/files/e19cffe4-e2da-47e9-809e-904f5cb03687 -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" -curl -XDELETE http://localhost:5001/api/v1/files/e19cffe4-e2da-47e9-809e-904f5cb03687 -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" +#curl http://192.168.3.6:5001/api/v1/files/create -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" -d '{"filename": "file.txt", "org_id": "b199646b-16d2-456d-9fd6-b9972e929466", "workflow_id": "global"}' +# +#curl http://localhost:5001/api/v1/files/create -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" -d '{"filename": "file.txt", "org_id": "b199646b-16d2-456d-9fd6-b9972e929466", "workflow_id": "global"}' +# +#echo +#curl http://localhost:5001/api/v1/files/e19cffe4-e2da-47e9-809e-904f5cb03687/upload -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" -F 'shuffle_file=@files.sh' +# +#curl http://localhost:5001/api/v1/files/1915981b-b897-4db1-8a2e-44bc34cead3b/content -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" +#curl http://localhost:5001/api/v1/files/e19cffe4-e2da-47e9-809e-904f5cb03687 -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" +#curl -XDELETE http://localhost:5001/api/v1/files/e19cffe4-e2da-47e9-809e-904f5cb03687 -H "Authorization: Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4" #r.HandleFunc("/api/v1/files/{fileId}/content", handleGetFileContent).Methods("GET", "OPTIONS") #r.HandleFunc("/api/v1/files/create", handleCreateFile).Methods("POST", "OPTIONS") #r.HandleFunc("/api/v1/files/{fileId}/upload", handleUploadFile).Methods("POST", "OPTIONS") #r.HandleFunc("/api/v1/files/{fileId}", handleGetFileMeta).Methods("GET", "OPTIONS") #r.HandleFunc("/api/v1/files/{fileId}", handleDeleteFile).Methods("DELETE", "OPTIONS") + + +#curl http://localhost:5001/api/v1/files/create -H "Authorization: Bearer c5b4c827-65ec-47f4-9e8a-234cdba38959" -d '{"filename": "rule2.yar", "org_id": "b4e88fe9-352b-47b4-b280-960181670acf", "workflow_id": "global", "namespace": "yara"}' +#curl http://localhost:5001/api/v1/files/5cb941ad-fa1c-4444-a685-92024b1fa31c/upload -H "Authorization: Bearer c5b4c827-65ec-47f4-9e8a-234cdba38959" -F 'shuffle_file=@upload.sh' + +curl http://localhost:5001/api/v1/files/namespaces/yara -H "Authorization: Bearer c5b4c827-65ec-47f4-9e8a-234cdba38959" --output rules.zip diff --git a/docker-compose.yml b/docker-compose.yml index 61a406c9..e804bb4f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,18 +25,18 @@ services: - "${BACKEND_PORT}:5001" networks: - shuffle - volumes: - - /var/run/docker.sock:/var/run/docker.sock - - ${SHUFFLE_APP_HOTLOAD_LOCATION}:/shuffle-apps + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ${SHUFFLE_APP_HOTLOAD_LOCATION}:/shuffle-apps - ${SHUFFLE_FILE_LOCATION}:/shuffle-files #- ${SHUFFLE_OPENSEARCH_CERTIFICATE_FILE}:/shuffle-files/es_certificate - env_file: .env + env_file: .env environment: - SHUFFLE_APP_HOTLOAD_FOLDER=/shuffle-apps - SHUFFLE_FILE_LOCATION=/shuffle-files restart: unless-stopped #depends_on: - #- opensearch + #- opensearch #Not necessary because dependancy is handled within the backend itself instead #- database orborus: #build: ./functions/onprem/orborus @@ -62,11 +62,11 @@ services: - SHUFFLE_PASS_WORKER_PROXY=${SHUFFLE_PASS_WORKER_PROXY} - SHUFFLE_PASS_APP_PROXY=${SHUFFLE_PASS_APP_PROXY} - SHUFFLE_ORBORUS_EXECUTION_TIMEOUT=600 - - SHUFFLE_ORBORUS_EXECUTION_CONCURRENCY=50 + - SHUFFLE_ORBORUS_EXECUTION_CONCURRENCY=5 - CLEANUP=${SHUFFLE_CONTAINER_AUTO_CLEANUP} restart: unless-stopped opensearch: - image: opensearchproject/opensearch:1.0.0 + image: opensearchproject/opensearch:1.1.0 hostname: shuffle-opensearch container_name: shuffle-opensearch environment: @@ -84,7 +84,7 @@ services: soft: -1 hard: -1 nofile: - soft: 65536 # maximum number of open files for the OpenSearch user, set to at least 65536 on modern systems + soft: 65536 hard: 65536 volumes: - ${DB_LOCATION}:/usr/share/opensearch/data:rw @@ -93,21 +93,6 @@ services: networks: - shuffle restart: unless-stopped - #database: - # #build: ./backend/database - # image: frikky/shuffle:database - # container_name: shuffle-database - # hostname: shuffle-database - # ports: - # - "8000:8000" - # networks: - # - shuffle - # environment: - # - _JAVA_OPTIONS="-Xmx2g" - # restart: unless-stopped - # volumes: - # - ${DB_LOCATION}:/etc/shuffle - networks: shuffle: driver: bridge diff --git a/frontend/Dockerfile b/frontend/Dockerfile index ab5940b1..12ba0b27 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -8,7 +8,6 @@ ENV PATH /usr/src/app/node_modules/.bin:$PATH COPY package.json /usr/src/app/package.json -#RUN npm install --verbose RUN yarn install # copy only required files to not trigger rebuilding every time @@ -21,7 +20,7 @@ COPY ./*.json /usr/src/app/ RUN yarn build # Production environment -FROM nginx:1.21 +FROM nginx:1.21.3 RUN mkdir -p /usr/share/nginx/html/build RUN mkdir -p /usr/share/nginx/html/css diff --git a/frontend/package.json b/frontend/package.json index 5b290522..14744ca8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,14 +1,16 @@ { "name": "shuffler", "homepage": "https://shuffler.io", - "version": "0.8.92", + "version": "0.9.24", "private": true, "dependencies": { "@material-ui/core": "^4.5.2", "@material-ui/data-grid": "^4.0.0-alpha.22", - "@material-ui/icons": "^4.5.1", + "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.58", "@material-ui/styles": "^4.5.2", + "@material-ui/utils": "^4.11.2", + "@uiw/react-codemirror": "^3.2.1", "@use-it/interval": "^1.0.0", "babel-eslint": "^10.1.0", "class-transformer": "^0.3.1", @@ -17,23 +19,23 @@ "cytoscape-clipboard": "^2.2.1", "cytoscape-cxtmenu": "^3.1.1", "cytoscape-edgehandles": "^3.6.0", - "cytoscape-grid-guide": "~2.1.2", + "cytoscape-grid-guide": "~2.3.3", "cytoscape-node-html-label": "^1.1.5", "cytoscape-panzoom": "^2.5.2", "cytoscape-undo-redo": "^1.3.2", - "d3": "~4.10.0", + "d3": "^7.1.1", "dotenv": "^6.1.0", "downshift": "^3.3.5", "github-markdown-css": "^3.0.1", "import": "0.0.6", "interweave": "^11.2.0", - "material-icons": "^0.3.1", + "material-icons": "^0.7.7", "material-icons-react": "^1.0.4", "material-ui-chip-input": "^2.0.0-beta.2", "material-ui-nested-menu-item": "^1.0.2", "md5-file": "^4.0.0", "mdbreact": "^4.21.1", - "moment": "~2.20.1", + "moment": "^2.29.1", "react": "^16.14.0", "react-alert": "^5.5.0", "react-alert-template-basic": "^1.0.0", @@ -64,10 +66,10 @@ "websocket": "^1.0.30", "yaml": "^1.7.2", "yamljs": "^0.3.0", - "zone.js": "~0.8.26" + "zone.js": "~0.11.4" }, "scripts": { - "start": "set HTTPS=true&&react-scripts start", + "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" diff --git a/frontend/public/images/Shuffle_logo.png b/frontend/public/images/Shuffle_logo.png new file mode 100644 index 00000000..077dc87e Binary files /dev/null and b/frontend/public/images/Shuffle_logo.png differ diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 70df8462..c4ef9a57 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -22,6 +22,8 @@ import AdminSetup from "./views/AdminSetup"; import Admin from "./views/Admin"; import Docs from "./views/Docs"; import Introduction from "./views/Introduction"; +import SetAuthentication from "./views/SetAuthentication"; +import SetAuthenticationSSO from "./views/SetAuthenticationSSO"; import LandingPageNew from "./views/LandingpageNew"; import LoginPage from "./views/LoginPage"; @@ -40,14 +42,15 @@ import {isMobile} from "react-device-detect"; var globalUrl = window.location.origin // CORS used for testing purposes. Should only happen with specific port and http -if (window.location.protocol == "http:" && window.location.port === "3000") { +if ( window.location.port === "3000") { globalUrl = "http://localhost:5001" //globalUrl = "http://localhost:5002" } const App = (message, props) => { const [userdata, setUserData] = useState({}); - const [cookies, setCookie, removeCookie] = useCookies([]); + const [notifications, setNotifications] = useState([]) + const [cookies, setCookie, removeCookie] = useCookies([]) const [isLoggedIn, setIsLoggedIn] = useState(false); const [dataset, setDataset] = useState(false); const [isLoaded, setIsLoaded] = useState(false); @@ -55,6 +58,7 @@ const App = (message, props) => { useEffect(() => { if (dataset === false) { + getUserNotifications() checkLogin() setDataset(true) } @@ -64,6 +68,25 @@ const App = (message, props) => { window.location = "/login" } + const getUserNotifications = () => { + fetch(`${globalUrl}/api/v1/notifications`, { + credentials: "include", + headers: { + 'Content-Type': 'application/json', + }, + }) + .then(response => response.json()) + .then(responseJson => { + if (responseJson.success === true && responseJson.notifications !== null && responseJson.notifications !== undefined && responseJson.notifications.length > 0) { + //console.log("RESP: ", responseJson) + setNotifications(responseJson.notifications) + } + }) + .catch(error => { + console.log("Failed getting notifications for user: ", error) + }); + } + const checkLogin = () => { var baseurl = globalUrl fetch(baseurl + "/api/v1/users/getinfo", { @@ -75,7 +98,7 @@ const App = (message, props) => { .then(response => response.json()) .then(responseJson => { if (responseJson.success === true) { - //console.log(responseJson.success) + console.log(responseJson) setUserData(responseJson) setIsLoggedIn(true) //console.log("Cookies: ", cookies) @@ -104,9 +127,10 @@ const App = (message, props) => { } /> :
- -
- } /> + +
+
+ } /> } /> } /> } /> @@ -125,6 +149,8 @@ const App = (message, props) => { { window.location.pathname = "/docs/about" }} /> } /> } /> + } /> + } /> } />
diff --git a/frontend/src/components/AlertTemplate.js b/frontend/src/components/AlertTemplate.js index 58ff46cc..66f4b1c7 100644 --- a/frontend/src/components/AlertTemplate.js +++ b/frontend/src/components/AlertTemplate.js @@ -15,7 +15,7 @@ const alertStyle = { justifyContent: 'space-between', alignItems: 'center', boxShadow: '0px 2px 2px 2px rgba(0, 0, 0, 0.03)', - width: 400, + width: 300, boxSizing: 'border-box', zIndex: 100001, overflow: "hidden", diff --git a/frontend/src/components/ConfigureWorkflow.jsx b/frontend/src/components/ConfigureWorkflow.jsx index 0cc6182a..43b93d33 100644 --- a/frontend/src/components/ConfigureWorkflow.jsx +++ b/frontend/src/components/ConfigureWorkflow.jsx @@ -11,8 +11,8 @@ import { FixName } from "../views/Apps.jsx"; // Triggers // // Specifically used for UNSAVED workflows only? -const Workflow = (props) => { - const { globalUrl, theme, workflow, appAuthentication, setSelectedAction, setAuthenticationModalOpen, setSelectedApp, apps, selectedAction,setConfigureWorkflowModalOpen, saveWorkflow, newWebhook, submitSchedule, referenceUrl, isCloud, } = props +const ConfigureWorkflow = (props) => { + const { globalUrl, theme, workflow, appAuthentication, setSelectedAction, setAuthenticationModalOpen, setSelectedApp, apps, selectedAction,setConfigureWorkflowModalOpen, saveWorkflow, newWebhook, submitSchedule, referenceUrl, isCloud, setAuthenticationType, alert, } = props const [requiredActions, setRequiredActions] = React.useState([]) const [requiredVariables, setRequiredVariables] = React.useState([]) const [requiredTriggers, setRequiredTriggers] = React.useState([]) @@ -90,7 +90,7 @@ const Workflow = (props) => { const app = apps.find(app => app.name === action.app_name && (app.app_version === action.app_version || (app.loop_versions !== null && app.loop_versions.includes(action.app_version)))) if (app === undefined || app === null) { - console.log("App not found!") + console.log("App not found: ", action.app_name) newaction.must_activate = true } else { @@ -374,6 +374,17 @@ const Workflow = (props) => { : + : null} + + ) + } + + const notificationMenu = + + { setAnchorEl(event.currentTarget); }}> - + + + { + handleClose() + }} + > + +
+ + Your Notifications ({notifications.length}) + + {notifications.length > 1 ? + + : null} +
+ + Notifications are made by Shuffle to help you discover issues or improvements. + +
+ {notifications.map((data, index) => { + return ( + + ) + })} +
+
+ + // Should be based on some path + const avatarMenu = + + { + setAnchorElAvatar(event.currentTarget); + }}> + + + { handleClose() }} @@ -139,7 +342,7 @@ const Header = props => { handleClose() }}> - Settings + Settings { @@ -147,7 +350,7 @@ const Header = props => { handleClose() handleClickLogout() }}> - Logout +  Logout @@ -155,8 +358,9 @@ const Header = props => { // Handle top bar or something const logoCheck = !homePage ? null : null + //
const loginTextBrowser = !isLoggedIn ? -
+
@@ -232,6 +436,7 @@ const Header = props => {
{avatarMenu} + {notificationMenu} {userdata === undefined || userdata.admin === undefined || userdata.admin === null || !userdata.admin ? null : } + {userdata === undefined || userdata.orgs === undefined || userdata.orgs === null || userdata.orgs.length <= 1 ? null : + + }
+ //console.log("USR: ", userdata.orgs) + const loginTextMobile = !isLoggedIn ?
@@ -300,7 +551,7 @@ const Header = props => { // const loadedCheck = -
+
{loginTextBrowser} @@ -310,10 +561,10 @@ const Header = props => {
//
return ( -
- {loadedCheck} +
+ {loadedCheck}
- ) + ) } export default Header; diff --git a/frontend/src/components/Oauth2Auth.jsx b/frontend/src/components/Oauth2Auth.jsx new file mode 100644 index 00000000..4fd89ac7 --- /dev/null +++ b/frontend/src/components/Oauth2Auth.jsx @@ -0,0 +1,417 @@ +import React, {useRef, useState, useEffect, useLayoutEffect} from 'react'; +import { useTheme } from '@material-ui/core/styles'; + +import { v4 as uuidv4 } from 'uuid'; +import { ListItemText, TextField, Drawer, Button, Paper, Grid, Tabs, InputAdornment, Tab, ButtonBase, Tooltip, Select, MenuItem, Divider, Dialog, Modal, DialogActions, DialogTitle, InputLabel, DialogContent, FormControl, IconButton, Menu, Input, FormGroup, FormControlLabel, Typography, Checkbox, Breadcrumbs, CircularProgress, Switch, Fade } from '@material-ui/core'; +import { LockOpen as LockOpenIcon } from '@material-ui/icons'; + +const ITEM_HEIGHT = 55 +const ITEM_PADDING_TOP = 8 +const MenuProps = { + PaperProps: { + style: { + maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, + minWidth: 500, + maxWidth: 500, + scrollX: "auto", + }, + }, +} + +const AuthenticationOauth2 = (props) => { + const { saveWorkflow, selectedApp, workflow, selectedAction, authenticationType, getAppAuthentication, appAuthentication, setSelectedAction, setNewAppAuth, setAuthenticationModalOpen} = props; + const theme = useTheme(); + + //const [update, setUpdate] = React.useState("|") + const [defaultConfigSet, setDefaultConfigSet] = React.useState(authenticationType.client_id !== undefined && authenticationType.client_id !== null && authenticationType.client_id.length > 0 && authenticationType.client_secret !== undefined && authenticationType.client_secret !== null && authenticationType.client_secret.length > 0) + const [clientId, setClientId] = React.useState(defaultConfigSet ? authenticationType.client_id : "") + const [clientSecret, setClientSecret] = React.useState(defaultConfigSet ? authenticationType.client_secret : "") + const [oauthUrl, setOauthUrl] = React.useState("") + const [buttonClicked, setButtonClicked] = React.useState(false) + const [selectedScopes, setSelectedScopes] = React.useState([]) + const allscopes = authenticationType.scope !== undefined ? authenticationType.scope: [] + + const [manuallyConfigure, setManuallyConfigure] = React.useState(defaultConfigSet ? false : true) + const [authenticationOption, setAuthenticationOptions] = React.useState({ + app: JSON.parse(JSON.stringify(selectedApp)), + fields: {}, + label: "", + usage: [{ + workflow_id: workflow.id, + }], + id: uuidv4(), + active: true, + }) + + if (selectedApp.authentication === undefined) { + return null + } + + const handleOauth2Request = (client_id, client_secret, oauth_url, scopes) => { + setButtonClicked(true) + console.log("SCOPES: ", scopes) + + var resources = "" + if (scopes !== undefined && scopes !== null & scopes.length > 0) { + resources = scopes.join(",") + } + + const authentication_url = authenticationType.token_uri + + console.log("SCOPES2: ", resources) + const redirectUri = `${window.location.protocol}//${window.location.host}/set_authentication` + var state = `workflow_id%3D${workflow.id}%26reference_action_id%3d${selectedAction.app_id}%26app_name%3d${selectedAction.app_name}%26app_id%3d${selectedAction.app_id}%26app_version%3d${selectedAction.app_version}%26authentication_url%3d${authentication_url}%26scope%3d${resources}%26client_id%3d${client_id}%26client_secret%3d${client_secret}` + if (oauth_url !== undefined && oauth_url !== null && oauth_url.length > 0) { + state += `%26oauth_url%3d${oauth_url}` + console.log("ADDING OAUTH2 URL: ", state) + } + const url = `${authenticationType.redirect_uri}?client_id=${client_id}&redirect_uri=${redirectUri}&response_type=code&scope=${resources}&prompt=consent&state=${state}` + + //const url = `https://accounts.zoho.com/oauth/v2/auth?response_type=code&client_id=${client_id}&scope=AaaServer.profile.Read&redirect_uri=${redirectUri}&prompt=consent` + console.log("Full URI: ", url) + console.log("Redirect Uri: ", redirectUri) + // &resource=https%3A%2F%2Fgraph.microsoft.com& + + // FIXME: Awful, but works for prototyping + // How can we get a callback properly realtime? + // How can we properly try-catch without breaks on error? + try { + + var newwin = window.open(url, "", "width=800,height=600") + //console.log(newwin) + + var open = true + const timer = setInterval(() => { + if (newwin.closed) { + setButtonClicked(false) + clearInterval(timer); + //alert('"Secure Payment" window closed!'); + + getAppAuthentication(true, true) + } + }, 1000); + //do { + // setTimeout(() => { + // console.log(newwin) + // console.log("CLOSED", newwin.closed) + // if (newwin.closed) { + + // open = false + // } + // }, 1000) + //} + //while(open === true) + } catch (e) { + alert.error("Failed authentication - probably bad credentials. Try again") + setButtonClicked(false) + } + + return + //do { + //} while ( + } + + + authenticationOption.app.actions = [] + + for (var key in selectedApp.authentication.parameters) { + if (authenticationOption.fields[selectedApp.authentication.parameters[key].name] === undefined) { + authenticationOption.fields[selectedApp.authentication.parameters[key].name] = "" + } + } + + const handleSubmitCheck = () => { + console.log("NEW AUTH: ", authenticationOption) + if (authenticationOption.label.length === 0) { + authenticationOption.label = `Auth for ${selectedApp.name}` + //alert.info("Label can't be empty") + //return + } + + // Automatically mapping fields that already exist (predefined). + // Warning if fields are NOT filled + for (var key in selectedApp.authentication.parameters) { + if (authenticationOption.fields[selectedApp.authentication.parameters[key].name].length === 0) { + if (selectedApp.authentication.parameters[key].value !== undefined && selectedApp.authentication.parameters[key].value !== null && selectedApp.authentication.parameters[key].value.length > 0) { + authenticationOption.fields[selectedApp.authentication.parameters[key].name] = selectedApp.authentication.parameters[key].value + } else { + if (selectedApp.authentication.parameters[key].schema.type === "bool") { + authenticationOption.fields[selectedApp.authentication.parameters[key].name] = "false" + } else { + alert.info("Field "+selectedApp.authentication.parameters[key].name+" can't be empty") + return + } + } + } + } + + console.log("Action: ", selectedAction) + selectedAction.authentication_id = authenticationOption.id + selectedAction.selectedAuthentication = authenticationOption + if (selectedAction.authentication === undefined || selectedAction.authentication === null) { + selectedAction.authentication = [authenticationOption] + } else { + selectedAction.authentication.push(authenticationOption) + } + + setSelectedAction(selectedAction) + + var newAuthOption = JSON.parse(JSON.stringify(authenticationOption)) + var newFields = [] + for (const key in newAuthOption.fields) { + const value = newAuthOption.fields[key] + newFields.push({ + key: key, + value: value, + }) + } + + console.log("FIELDS: ", newFields) + newAuthOption.fields = newFields + setNewAppAuth(newAuthOption) + //appAuthentication.push(newAuthOption) + //setAppAuthentication(appAuthentication) + // + + //if (configureWorkflowModalOpen) { + // setSelectedAction({}) + //} + //setUpdate(authenticationOption.id) + + /* + {selectedAction.authentication.map(data => ( + + */ + + } + + const handleScopeChange = (event) => { + const { + target: { value }, + } = event; + + console.log("VALUE: ", value) + + // On autofill we get a the stringified value. + setSelectedScopes(typeof value === 'string' ? value.split(',') : value) + } + + + if (authenticationOption.label === null || authenticationOption.label === undefined) { + authenticationOption.label = selectedApp.name+" authentication" + } + + //console.log( + return ( +
+
Authentication for {selectedApp.name}
+ + + Oauth2 requires a client ID and secret to authenticate. This is usually made in the remote system. + Learn more about Oauth2 with Shuffle
+ + {/* { + authenticationOption.label = event.target.value + }} + /> + + */} + + {!manuallyConfigure ? null : + + {selectedApp.authentication.parameters.map((data, index) => { + //console.log(data, index) + if (data.name === "client_id" || data.name === "client_secret") { + return null + } + + if (data.name !== "url") { + return null + } + + if (oauthUrl.length === 0) { + setOauthUrl(data.value) + } + + return ( +
+ + {data.name} + + {data.schema !== undefined && data.schema !== null && data.schema.type === "bool" ? + + : + { + authenticationOption.fields[data.name] = event.target.value + console.log("Setting oauth url") + setOauthUrl(event.target.value) + //const [oauthUrl, setOauthUrl] = React.useState("") + }} + /> + } +
+ ) + })} + {allscopes.length === 0 ? null : + } + renderValue={(selected) => selected.join(', ')} + MenuProps={MenuProps} + > + {allscopes.map((data, index) => { + return ( + + -1} /> + + + ) + })} + + } + { + setClientId(event.target.value) + //authenticationOption.label = event.target.value + }} + /> + { + setClientSecret(event.target.value) + //authenticationOption.label = event.target.value + }} + /> +
+ } + + + {defaultConfigSet ? + + ... or + + + : + null + } + +
+ ) +} + +export default AuthenticationOauth2 diff --git a/frontend/src/components/OrgHeader.js b/frontend/src/components/OrgHeader.js index 5d86745a..65dd6e97 100644 --- a/frontend/src/components/OrgHeader.js +++ b/frontend/src/components/OrgHeader.js @@ -22,7 +22,7 @@ const useStyles = makeStyles({ const defaultImage = "data:image/png;base64,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" const OrgHeader = (props) => { - const {selectedOrganization, setSelectedOrganization, globalUrl} = props + const {userdata, selectedOrganization, setSelectedOrganization, globalUrl} = props const theme = useTheme() const alert = useAlert() @@ -36,6 +36,8 @@ const OrgHeader = (props) => { const [appDownloadBranch, setAppDownloadBranch] = React.useState(selectedOrganization.defaults === undefined ? defaultBranch : selectedOrganization.defaults.app_download_branch === undefined || selectedOrganization.defaults.app_download_branch.length === 0 ? defaultBranch : selectedOrganization.defaults.app_download_branch) const [workflowDownloadUrl, setWorkflowDownloadUrl] = React.useState(selectedOrganization.defaults === undefined ? "https://github.com/frikky/shuffle-apps" : selectedOrganization.defaults.workflow_download_repo === undefined || selectedOrganization.defaults.workflow_download_repo.length === 0 ? "https://github.com/frikky/shuffle-workflows" : selectedOrganization.defaults.workflow_download_repo) const [workflowDownloadBranch, setWorkflowDownloadBranch] = React.useState(selectedOrganization.defaults === undefined ? defaultBranch : selectedOrganization.defaults.workflow_download_branch === undefined || selectedOrganization.defaults.workflow_download_branch.length === 0 ? defaultBranch : selectedOrganization.defaults.workflow_download_branch) + const [ssoEntrypoint, setSsoEntrypoint] = React.useState(selectedOrganization.sso_config === undefined ? "" : selectedOrganization.sso_config.sso_entrypoint === undefined || selectedOrganization.sso_config.sso_entrypoint.length === 0 ? "" : selectedOrganization.sso_config.sso_entrypoint) + const [ssoCertificate, setSsoCertificate] = React.useState(selectedOrganization.sso_config === undefined ? "" : selectedOrganization.sso_config.sso_certificate === undefined || selectedOrganization.sso_config.sso_certificate.length === 0 ? "" : selectedOrganization.sso_config.sso_certificate) const [file, setFile] = React.useState("") const [fileBase64, setFileBase64] = React.useState(selectedOrganization.image) @@ -67,13 +69,14 @@ const OrgHeader = (props) => { } } - const handleEditOrg = (name, description, orgId, image, defaults) => { + const handleEditOrg = (name, description, orgId, image, defaults, sso_config) => { const data = { "name": name, "description": description, "org_id": orgId, "image": image, "defaults": defaults, + "sso_config": sso_config, } const url = globalUrl + `/api/v1/orgs/${selectedOrganization.id}`; @@ -110,20 +113,28 @@ const OrgHeader = (props) => { setFile(fileObject) } + console.log("USER: ", userdata) const orgSaveButton = - + + + var imageData = file.length > 0 ? file : fileBase64 imageData = imageData === undefined || imageData.length === 0 ? defaultImage : imageData @@ -337,6 +348,69 @@ const OrgHeader = (props) => { />
+ + + + SSO Entrypoint (IdP) + + 0} + id="outlined-with-placeholder" + margin="normal" + variant="outlined" + placeholder="The entrypoint URL from your provider" + value={ssoEntrypoint} + onChange={e => { + setSsoEntrypoint(e.target.value) + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style:{ + color: "white", + }, + }} + /> + + + + + + SSO Certificate (X509) + + { + setSsoCertificate(e.target.value) + }} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style:{ + color: "white", + }, + }} + /> + + {/* {expanded ? diff --git a/frontend/src/components/ParsedAction.jsx b/frontend/src/components/ParsedAction.jsx index 05cf9fb1..ef635b6f 100644 --- a/frontend/src/components/ParsedAction.jsx +++ b/frontend/src/components/ParsedAction.jsx @@ -8,10 +8,14 @@ import { useTheme } from '@material-ui/core/styles'; import NestedMenuItem from "material-ui-nested-menu-item"; //import NestedMenuItem from "./NestedMenu.jsx"; -import {Popper, TextField, Drawer, Button, Paper, Grid, Tabs, InputAdornment, Tab, ButtonBase, Tooltip, Select, MenuItem, Divider, Dialog, Modal, DialogActions, DialogTitle, InputLabel, DialogContent, FormControl, IconButton, Menu, Input, FormGroup, FormControlLabel, Typography, Checkbox, Breadcrumbs, CircularProgress, Switch, Fade} from '@material-ui/core'; -import {GetApp as GetAppIcon, Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Done as DoneIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, MoreVert as MoreVertIcon, Apps as AppsIcon, Schedule as ScheduleIcon, FavoriteBorder as FavoriteBorderIcon, Pause as PauseIcon, Delete as DeleteIcon, AddCircleOutline as AddCircleOutlineIcon, Save as SaveIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons'; +import {Popper, TextField, TextareaAutosize, Drawer, Button, Paper, Grid, Tabs, InputAdornment, Tab, ButtonBase, Tooltip, Select, MenuItem, Divider, Dialog, Modal, DialogActions, DialogTitle, InputLabel, DialogContent, FormControl, IconButton, Menu, Input, FormGroup, FormControlLabel, Typography, Checkbox, Breadcrumbs, CircularProgress, Switch, Fade} from '@material-ui/core'; +import {HelpOutline as HelpOutlineIcon, Description as DescriptionIcon, GetApp as GetAppIcon, Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Done as DoneIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, MoreVert as MoreVertIcon, Apps as AppsIcon, Schedule as ScheduleIcon, FavoriteBorder as FavoriteBorderIcon, Pause as PauseIcon, Delete as DeleteIcon, AddCircleOutline as AddCircleOutlineIcon, Save as SaveIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons'; import Autocomplete from '@material-ui/lab/Autocomplete'; +import CodeMirror from '@uiw/react-codemirror'; +import 'codemirror/keymap/sublime'; +import 'codemirror/theme/gruvbox-dark.css'; + const useStyles = makeStyles({ notchedOutline: { @@ -46,10 +50,13 @@ const useStyles = makeStyles({ //) const ParsedAction = (props) => { - const {workflow, setWorkflow, setAction, setSelectedAction, setUpdate, appActionArguments, selectedApp, workflowExecutions, setSelectedResult, selectedAction, setSelectedApp, setSelectedTrigger, setSelectedEdge, setCurrentView, cy, setAuthenticationModalOpen,setVariablesModalOpen, setCodeModalOpen, selectedNameChange, rightsidebarStyle, showEnvironment, selectedActionEnvironment, environments, setNewSelectedAction, appApiViewStyle, globalUrl, setSelectedActionEnvironment, requiresAuthentication, hideExtraTypes, scrollConfig, setScrollConfig } = props + const {workflow, setWorkflow, setAction, setSelectedAction, setUpdate, appActionArguments, selectedApp, workflowExecutions, setSelectedResult, selectedAction, setSelectedApp, setSelectedTrigger, setSelectedEdge, setCurrentView, cy, setAuthenticationModalOpen,setVariablesModalOpen, setCodeModalOpen, selectedNameChange, rightsidebarStyle, showEnvironment, selectedActionEnvironment, environments, setNewSelectedAction, appApiViewStyle, globalUrl, setSelectedActionEnvironment, requiresAuthentication, hideExtraTypes, scrollConfig, setScrollConfig, authenticationType, appAuthentication, getAppAuthentication } = props const theme = useTheme(); const classes = useStyles() + + const [expansionModalOpen, setExpansionModalOpen] = React.useState(false); + const keywords = ["len(", "lower(", "upper(", "trim(", "split(", "length(", "number(", "parse(", "join("] const getParents = (action) => { if (cy === undefined) { @@ -327,7 +334,8 @@ const ParsedAction = (props) => { } // 1. Take - const actionvalue = {"type": "action", "id": item.id, "name": item.label, "autocomplete": `${item.label.split(" ").join("_")}`, "example": exampledata} + const itemlabelComplete = item.label === null || item.label === undefined ? "" : item.label.split(" ").join("_") + const actionvalue = {"type": "action", "id": item.id, "name": item.label, "autocomplete": itemlabelComplete, "example": exampledata} actionlist.push(actionvalue) } } @@ -337,12 +345,13 @@ const ParsedAction = (props) => { }) const changeActionParameter = (event, count, data) => { + //console.log(event) if (data.name.startsWith("${") && data.name.endsWith("}")) { // PARAM FIX - Gonna use the ID field, even though it's a hack const paramcheck = selectedAction.parameters.find(param => param.name === "body") if (paramcheck !== undefined) { // Escapes all double quotes - const toReplace = event.target.value.trim().replaceAll("\\\"", "\"").replaceAll("\"", "\\\"") + const toReplace = event.target.value.trim().replaceAll("\\\"", "\"").replaceAll("\"", "\\\""); console.log("REPLACE WITH: ", toReplace) if (paramcheck["value_replace"] === undefined || paramcheck["value_replace"] === null) { paramcheck["value_replace"] = [{ @@ -472,6 +481,7 @@ const ParsedAction = (props) => { } } + //console.log("CHANGING ACTION COUNT !") selectedActionParameters[count].value = event.target.value selectedAction.parameters[count].value = event.target.value setSelectedAction(selectedAction) @@ -479,6 +489,150 @@ const ParsedAction = (props) => { //setUpdate(event.target.value) } + const changeActionParameterCodemirror = (event, count, data) => { + console.log(event) + if (data.name.startsWith("${") && data.name.endsWith("}")) { + // PARAM FIX - Gonna use the ID field, even though it's a hack + const paramcheck = selectedAction.parameters.find(param => param.name === "body") + if (paramcheck !== undefined) { + // Escapes all double quotes + const toReplace = event.target.value.trim().replaceAll("\\\"", "\"").replaceAll("\"", "\\\""); + console.log("REPLACE WITH: ", toReplace) + if (paramcheck["value_replace"] === undefined || paramcheck["value_replace"] === null) { + paramcheck["value_replace"] = [{ + "key": data.name, + "value": toReplace, + }] + + console.log("IN IF: ", paramcheck) + + } else { + const subparamindex = paramcheck["value_replace"].findIndex(param => param.key === data.name) + if (subparamindex === -1) { + paramcheck["value_replace"].push({ + "key": data.name, + "value": toReplace, + }) + } else { + paramcheck["value_replace"][subparamindex]["value"] = toReplace + } + + console.log("IN ELSE: ", paramcheck) + } + //console.log("PARAM: ", paramcheck) + //if (paramcheck.id === undefined) { + // console.log("Normal paramcheck") + //} else { + // selectedActionParameters[count]["value_replace"] = paramcheck + // selectedAction.parameters[count]["value_replace"] = paramcheck + //} + + if (paramcheck["value_replace"] === undefined) { + selectedActionParameters[count]["value_replace"] = paramcheck + selectedAction.parameters[count]["value_replace"] = paramcheck + } else { + selectedActionParameters[count]["value_replace"] = paramcheck["value_replace"] + selectedAction.parameters[count]["value_replace"] = paramcheck["value_replace"] + } + console.log("RESULT: ", selectedAction) + setSelectedAction(selectedAction) + //setUpdate(Math.random()) + return + } + } + + if (event.display.maxLine.text[event.display.maxLine.text.length-1] === "$") { + if (!showDropdown) { + setShowAutocomplete(false) + setShowDropdown(true) + setShowDropdownNumber(count) + } + } else { + if (showDropdown) { + setShowDropdown(false) + } + } + + // bad detection mechanism probably + if (event.display.maxLine.text[event.display.maxLine.text.length-1] === "." && actionlist.length > 0) { + console.log("GET THE LAST ARGUMENT FOR NODE!") + // THIS IS AN EXAMPLE OF SHOWING IT + /* + const inputdata = {"data": "1.2.3.4", "dataType": "4.5.6.6"} + setJsonList(GetParsedPaths(inputdata, "")) + if (!showDropdown) { + setShowAutocomplete(false) + setShowDropdown(true) + setShowDropdownNumber(count) + } + console.log(jsonList) + */ + + // Search for the item backwards + // 1. Reverse search backwards from . -> $ + // 2. Search the actionlist for the item + // 3. Find the data for the specific item + + var curstring = "" + var record = false + for (var key in selectedActionParameters[count].value) { + const item = selectedActionParameters[count].value[key] + if (record) { + curstring += item + } + + if (item === "$") { + record = true + curstring = "" + } + } + + //console.log("CURSTRING: ", curstring) + if (curstring.length > 0 && actionlist !== null) { + // Search back in the action list + curstring = curstring.split(" ").join("_").toLowerCase() + var actionItem = actionlist.find(data => data.autocomplete.split(" ").join("_").toLowerCase() === curstring) + if (actionItem !== undefined) { + console.log("Found item: ", actionItem) + + //actionItem.example = actionItem.example.trim() + //actionItem.example = actionItem.example.split(" None").join(" \"None\"") + //actionItem.example = actionItem.example.split("\'").join("\"") + + var jsonvalid = true + try { + const tmp = String(JSON.parse(actionItem.example)) + if (!actionItem.example.includes("{") && !actionItem.example.includes("[")) { + jsonvalid = false + } + } catch (e) { + jsonvalid = false + } + + if (jsonvalid) { + setJsonList(GetParsedPaths(JSON.parse(actionItem.example), "")) + + if (!showDropdown) { + setShowAutocomplete(false) + setShowDropdown(true) + setShowDropdownNumber(count) + } + } + } + } + } else { + if (jsonList.length > 0) { + setJsonList([]) + } + } + + selectedActionParameters[count].value = event.display.maxLine.text + selectedAction.parameters[count].value = event.display.maxLine.text + setSelectedAction(selectedAction) + //setUpdate(Math.random()) + //setUpdate(event.target.value) + } + const changeActionParameterVariable = (fieldvalue, count) => { //console.log("CALLED THIS ONE WITH VALUE!", fieldvalue) //if (selectedVariableParameter === fieldvalue) { @@ -599,6 +753,10 @@ const ParsedAction = (props) => { var placeholder = "Static value" if (data.example !== undefined && data.example !== null && data.example.length > 0) { placeholder = data.example + + if (data.name === "url" && data.value.length === 0) { + data.value = data.example + } } if (data.name.startsWith("${") && data.name.endsWith("}")) { @@ -684,10 +842,12 @@ const ParsedAction = (props) => { } const clickedFieldId = "rightside_field_"+count + // { maxWidth: "95%", fontSize: "1em", }, - endAdornment: ( - hideExtraTypes ? null : - - - { - setMenuPosition({ - top: event.pageY+10, - left: event.pageX+10, - }) - setShowDropdownNumber(count) - setShowDropdown(true) - setShowAutocomplete(true) - }}/> - - - - ) + endAdornment: ( + hideExtraTypes ? null : + + + { + setMenuPosition({ + top: event.pageY+10, + left: event.pageX+10, + }) + setShowDropdownNumber(count) + setShowDropdown(true) + setShowAutocomplete(true) + }}/> + + + + ) }} fullWidth multiline={multiline} onClick={() => { - //console.log("Clicked field: ", clickedFieldId) + console.log("Clicked field: ", clickedFieldId) + setExpansionModalOpen(false) if (setScrollConfig !== undefined && scrollConfig !== null && scrollConfig !== undefined && scrollConfig.selected !== clickedFieldId) { scrollConfig.selected = clickedFieldId setScrollConfig(scrollConfig) @@ -728,11 +889,22 @@ const ParsedAction = (props) => { rows={rows} color="primary" defaultValue={data.value} + //value={data.value} + //options={{ + // theme: 'gruvbox-dark', + // keyMap: 'sublime', + // mode: 'python', + //}} + //height={multiline ? 50 : 150} + type={placeholder.includes("***") || (data.configuration && (data.name.toLowerCase().includes("api") || data.name.toLowerCase().includes("key") || data.name.toLowerCase().includes("pass"))) ? "password" : "text"} placeholder={placeholder} + onChange={(event) => { - changeActionParameter(event, count, data) + //changeActionParameterCodemirror(event, count, data) + changeActionParameter(event, count, data) }} + helperText={selectedApp.generated && selectedApp.activated && data.name === "body" ? {openApiHelperText} @@ -790,7 +962,7 @@ const ParsedAction = (props) => { endAdornment: ( hideExtraTypes ? null : - + { setMenuPosition({ top: event.pageY+10, @@ -1102,6 +1274,13 @@ const ParsedAction = (props) => { } tmpitem = (tmpitem.charAt(0).toUpperCase()+tmpitem.substring(1)).replaceAll("_", " ") + + if (tmpitem === "Username basic") { + tmpitem = "Username" + } else if (tmpitem === "Password basic") { + tmpitem = "Password" + } + const description = data.description === undefined ? "" : data.description const tooltipDescription = @@ -1163,11 +1342,12 @@ const ParsedAction = (props) => {
*/} - {(selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && selectedActionParameters[count].options.length > 0 && selectedActionParameters[count].required === true && selectedActionParameters[count].unique_toggled !== undefined) || hideExtraTypes ? null : + {/*(selectedActionParameters[count].options !== undefined && selectedActionParameters[count].options !== null && selectedActionParameters[count].options.length > 0 && selectedActionParameters[count].required === true && selectedActionParameters[count].unique_toggled !== undefined) || hideExtraTypes ? null :
{}}> {
- } + */}
{datafield} {showDropdown && showDropdownNumber === count && data.variant === "STATIC_VALUE" && jsonList.length > 0 ? @@ -1256,6 +1436,27 @@ const ParsedAction = (props) => { return null } + const expansionModal = + { + setExpansionModalOpen(false) + }} + PaperProps={{ + style: { + backgroundColor: theme.palette.surfaceColor, + color: "white", + minWidth: 600, + padding: 50, + }, + }} + > + Workflow Variable + + Hello + + + //const CustomPopper = function (props) { // const classes = useStyles() // return @@ -1264,12 +1465,13 @@ const ParsedAction = (props) => { const baselabel = selectedAction.label return (
+ {expansionModal} {hideExtraTypes === true ? null :

{(selectedAction.app_name.charAt(0).toUpperCase()+selectedAction.app_name.substring(1)).replaceAll("_", " ")}

-
+
{ console.log("FIND EXAMPLE RESULTS FOR ", selectedAction) if (workflowExecutions.length > 0) { @@ -1298,19 +1500,24 @@ const ParsedAction = (props) => { - - What are actions? - {selectedAction.errors !== undefined && selectedAction.errors !== null && selectedAction.errors.length > 0 ? -
- Errors: {selectedAction.errors.join("\n")} -
- : null - } -
+ { + setAuthenticationModalOpen(true) + }}> + + + + + {}}> + + + + + +
- {selectedAction.id === workflow.start ? null : + {/*selectedAction.id === workflow.start ? null : - } + */} {selectedApp.versions !== null && selectedApp.versions !== undefined && selectedApp.versions.length > 1 ? { }} fullWidth onChange={(e) => { - //console.log("CHOSE AN AUTHENTICATION OPTION: ", e.target.value) - selectedAction.selectedAuthentication = e.target.value - selectedAction.authentication_id = e.target.value.id - setSelectedAction(selectedAction) - setUpdate(Math.random()) + if (e.target.value === "No selection") { + selectedAction.selectedAuthentication = {} + selectedAction.authentication_id = "" + + for (var key in selectedAction.parameters) { + //console.log(selectedAction.parameters[key]) + if (selectedAction.parameters[key].configuration) { + selectedAction.parameters[key].value = "" + } + } + setSelectedAction(selectedAction) + setUpdate(Math.random()) + } else { + //console.log("CHOSE AN AUTHENTICATION OPTION: ", e.target.value) + selectedAction.selectedAuthentication = e.target.value + selectedAction.authentication_id = e.target.value.id + setSelectedAction(selectedAction) + setUpdate(Math.random()) + } }} style={{backgroundColor: theme.palette.inputColor, color: "white", height: 50, maxWidth: rightsidebarStyle.maxWidth-80, borderRadius: theme.palette.borderRadius,}} > + + + No selection + {selectedAction.authentication.map(data => { //console.log("AUTH DATA: ", data) return( @@ -1511,7 +1742,7 @@ const ParsedAction = (props) => { No selection - + {workflow.execution_variables.map(data => ( {data.name} diff --git a/frontend/src/components/ScrollToTop.jsx b/frontend/src/components/ScrollToTop.jsx index 6b3c9911..88d2d158 100644 --- a/frontend/src/components/ScrollToTop.jsx +++ b/frontend/src/components/ScrollToTop.jsx @@ -1,8 +1,7 @@ import { useEffect } from 'react'; import { withRouter } from 'react-router-dom'; -import ReactGA from 'react-ga'; -function ScrollToTop({setCurpath, history }) { +function ScrollToTop({getUserNotifications, setCurpath, history }) { useEffect(() => { const unlisten = history.listen(() => { window.scroll({ @@ -11,14 +10,8 @@ function ScrollToTop({setCurpath, history }) { behavior: "smooth", }); - //ReactGA.event({ - // category: "referral", - // action: "new_user_referral", - // label: "", - //}) - - ReactGA.pageview(window.location.pathname) setCurpath(window.location.pathname) + getUserNotifications() }); return () => { unlisten(); diff --git a/frontend/src/css/font1.woff2 b/frontend/src/css/font1.woff2 new file mode 100644 index 00000000..ecc0a77d Binary files /dev/null and b/frontend/src/css/font1.woff2 differ diff --git a/frontend/src/css/font2.woff2 b/frontend/src/css/font2.woff2 new file mode 100644 index 00000000..81bd7f78 Binary files /dev/null and b/frontend/src/css/font2.woff2 differ diff --git a/frontend/src/css/font3.woff2 b/frontend/src/css/font3.woff2 new file mode 100644 index 00000000..18c97b06 Binary files /dev/null and b/frontend/src/css/font3.woff2 differ diff --git a/frontend/src/css/font4.woff2 b/frontend/src/css/font4.woff2 new file mode 100644 index 00000000..786e79a0 Binary files /dev/null and b/frontend/src/css/font4.woff2 differ diff --git a/frontend/src/css/font5.woff2 b/frontend/src/css/font5.woff2 new file mode 100644 index 00000000..695aa98b Binary files /dev/null and b/frontend/src/css/font5.woff2 differ diff --git a/frontend/src/css/nunito.css b/frontend/src/css/nunito.css new file mode 100644 index 00000000..bbf07d1d --- /dev/null +++ b/frontend/src/css/nunito.css @@ -0,0 +1,40 @@ +/* cyrillic-ext */ +@font-face { + font-family: 'Nunito Sans'; + font-style: normal; + font-weight: 400; + src: url('./font1.woff2') format('woff2'); + unicode-range: U+0460-052F, U+1C80-1C88, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F; +} +/* cyrillic */ +@font-face { + font-family: 'Nunito Sans'; + font-style: normal; + font-weight: 400; + src: url('./font2.woff2') format('woff2'); + unicode-range: U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116; +} +/* vietnamese */ +@font-face { + font-family: 'Nunito Sans'; + font-style: normal; + font-weight: 400; + src: url('./font3.woff2') format('woff2'); + unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+1EA0-1EF9, U+20AB; +} +/* latin-ext */ +@font-face { + font-family: 'Nunito Sans'; + font-style: normal; + font-weight: 400; + src: url('./font4.woff2') format('woff2'); + unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Nunito Sans'; + font-style: normal; + font-weight: 400; + src: url('./font5.woff2') format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} diff --git a/frontend/src/index.css b/frontend/src/index.css index 43ec82ff..793c42e5 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,4 +1,4 @@ -@import url('https://fonts.googleapis.com/css?family=Nunito+Sans'); +@import url('./css/nunito.css'); body { margin: 0; diff --git a/frontend/src/theme.js b/frontend/src/theme.js index f5424ea8..92371aad 100644 --- a/frontend/src/theme.js +++ b/frontend/src/theme.js @@ -12,6 +12,7 @@ const theme = createMuiTheme({ text: { secondary: "rgba(255,255,255,0.7)", }, + type: 'dark', surfaceColor: "#27292d", inputColor: "#383B40", borderRadius: 5, @@ -33,6 +34,7 @@ const theme = createMuiTheme({ boxShadow: 1, fontSize: 11, }, + defaultImage: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAK4AAACuCAYAAACvDDbuAAAgAElEQVR4Xu19e9CvV1Xe3r/vnJOcBEhBSgMEBaoUK9POCOVmAuP0HwcUCNYZSUsh9xv3hGl1qNippQRE20IFEhQoBJiaKVpEgQRnhD+0QHSmRkAsxE4doBZQTs71u/zezruv6/Ksvffvkn/qd8bBfN/3XvZe+1nPevbaa+/XuxX/Tbe6C/ece8qOd5dNk3um9+7Jk/OPds49zE3uyPy4ST5zCr/y4f+sfxO4j16vHsqfmV7gpgm8Yzm/lP9+km1B9+W2zn/zzk2sDeR55ffy3enn1Lf5J/1e3bb42kX43/Do1nt9fUdpLrSbbFt8vvls+idlm/qsaJPWuNK/TfvOLU44577pnPuSc9PvT95/9szu3p9c/IH/c2oVKDbeyB8z/Yz7noNd9zzn3U+5hX+6m9wjnXM7GqXCHpbR0+PnjudGxEtBkzRo02vFtRB83rkAXPqPGD4MnmWGGa3CDqp9+pp4Bwd2dCwPzIXur6D1xaGRXaQz8nf4Kfix1/3joDXtDm0jHVbYspirbZdkj4Npcv/XO/8F59xdfv/o7zz0A1/9yxEAd4E7/by74OCke5Fb+Bvd5J7unDvGHmyCKiMzX40ByW+XQCxvyoSafgGMBT0fgTayXmA/kykr8PI1kS0JIOMPRiSJ7ctOWfuI+mcw4Uj7lO25Y0TQoyHGbKsjJbqXR5HoKijK6nuz3bPhokEX+f4956Z7nXP/8cz+w//bxR/4H00GbgJ3ep170sHC/WxgWecvUJ5AemrLA4NF2cCAa4BDSCbjYVR2RYN2ZXlAOgxZqcXUDLh9toyvSkybopAJ+KBbOpHADOEjoO1EkVYU6LB0HbPUDtaN8N7TzrmPOnfwry9631/8mcW+JnD3Xu+eu3DubZNzT1Xhu8eyxaXw47lWBIzcAW2fGSpoM1WPgzYNWqSRwqjsnSYokgMyvW5JnzWZNrQpC9tC+ZVZW1Ek9GbBQK8ZuS99mrrW1sPh7XXsTeDmqHrvYvKve+j77/8MAi9E1v6t7vnOu7c7555gaM4SuiHGOsajcV8xmeEUMSDFf4yJQLiUmpYNTg906iVCl7b6ltu4gi5lTMs6Z4RpwLQsVMP+IZaNTtYngRoNI1uOzwdYhJxtEhqKAFsdvoI0XHy/m3ZeedH7v/ZxCV7Vir1b3HP8wr3PBC1HDsog5IkJxSdDHAyBlRqBgyHwAANuI3sg3j4G+shSHASQaRlbZtDW8AmiT7b3TLJD8qACrWrwkclYg2l70qACWow5IZvQ9lHQUmeZ7p+8v/Lh7/3z36NDw6w7a9rljvvw5NwPWykOOuEono5YT8IvXQNBK5yhGnwFVoCgZYPYZgvSBtnGMMkJnR2ZrIh3Fga3mGoR9YgpL2xAjbGlBC2YTEGWJu01J3mkrwYGuFP25EF1XMFj9y6Wiyse9p+/9hXl2il78G7n3T9radomMVphpMdGUG9wAJRLYJ4Wz2CZ73QmUpkxa9jlA2fmKvNz0414Fm+Abw6b+U8D4Km+xfsb0l5E99Z+S4mApIHhaEz2bCAPSmMkaNMzW4Av94ac3vtPHdu96TG3f32evFXhuH+ru8J5f4dzrmYPVGiKN7Bfsx/syVj+C07AA3WQmlYBa4TR+bpWnhYCgjyLtN+OBrbezPdAwNcQCjq4EAsaCEAS8BF4PNIZoBKhWdo9PMO0DX5vbmGTRMi41SgyxrQlTcmsVQB+2jt/3UXv/9qdxVLTre5RBwv/m25yz+Qspe2NQWt5ZEQ5x79kMgzaoTC4DM9SuoqDvcEWSqKsNlnJHYvvQ5oWvRutiInrijdwALGBLZ003sEcMl7DSEdHoGTHyoQ4eqTxaqymzdmYuq40wrS1D5A80i+985/1u0de/LAPf+Vb4Y7917uXO+dud84fRcClIOKdVwPGQZRuNBuj6DujMHakCUCgadUsubeMS1A/vhRLBxZEgR6g8juLbUzAE1tWe8wslv9hh9HyQPknZNpEAsUQaJWPDgpttyCjsDxuTMQMwAOiUoTknNv1frrmovfd/wE/1x4cTP4jbuF+3AItwFe81ACGXm0SAC/5UTYGoSBAdwCwmZE9GAqhInmvBr8JPMmAo0zLB7G5YmdMALkTy/fOz5+NonO0VaJl0Bmyh3jDakzL2xL6JnLF8dGjk8xKDBWPtc3euY+dWT7kJX73VveMhfe/4Zy7ONOcgDpnP+a+iC1WLpZJ7RPhArLCPD7snZSVpnmGkgMZTdIrA5BIwOoHBkDLHWs1eRBMZ/Wr2FWGTcFmGQSMZYhjAInAB1C2WQIlrBGUi1JtUaJ8DHrtVIJtwyjZ6bxKjA3Q1n590y8Wl/uDW9wt08LfFgpmCCgx81FroU40KqhUvNLPYu+0NBSYiBUckjDHxtX0eCJJeqBN7cfh2WazKa/FK4MK+wGmHV/t40wbmyoBj8erYDQ5VGYC1VzFmBZpjUzEjGIjhREO5NS2A+f9v/T7t7q7nPc/qZPbYuhZeJdGL6lIMgFQ12gsJUBxwObLxP1KHujO+8nQZT15ANiOsrRcooZVXjDEyyovAzypbDKNQRifdSZiFXTI9m2JQCNBZcAcv+x7w9iF/zEACwlLZEd6ZZiFmWI7vJvu8gevd/dNk/+hPFDa02rwtXOZdZij8axlUoldwnilAYaRDKa1c5ixizxMVqfAEcUIo122RW2OCwv8/aPSotolLn5Y+nBkRWxk4aQuqGu7GOMxE5nPtQcN0DLggvGGYySjRR3L1L77/P6t/tvOuUc0gdvSZb0qL+hxtbSw/tkGma49SB3J2sl4R0SOBqNm+AYbFW9Hz0KgqIPIgWsCgHhzvaZ9rwYsjgKyzZiJMVmNVoiNsi3oWxi3BlkUwshMW7JO35mBu+dc3LmgwNvUfZVNasfH5AHVX/kOvHNBDzbXfX3jZkxnThliwSwtSswcLQDng7haFVUEWb99umAm6+5iyzA0yDbcntDmhQSwo2U5EvPzI3pWOzzDC/OaLtMm0Pm9GbhVpkD0hheX9hZwpxdWPYQ6iiqMweCgUMizB7nBYtXOeKeo0optXC1Mg8WFagMzAtV62mpKwCiC4miBdcCNSRg2aOnQNSu4Amn5MAi2LGyzoF2aaDtLflfCS2kDtBPRtBVf+cr4Dr+XgMs7Tn9CA86uNkDBr4k/AY0jQ0XRsoIdUqV9NXjLUXrywAJ8Yj2LBYrXovsrqMwI1Mhlhn6xUWq/I9tzTKsT1hNO15dNkqV5VFHAE7bTzpGep/6Afi8jXXWMBnBB2CeGXVUeaNAaYV5kD3IYLP5mFpTwlFSkx9VraWv1m+UYNqB46AL2Q6yf2lgr0HRoDX1PBTk1EliEgkO8dPhcLs+Ba9ybTRsuXmVFLI6Cwgoihh7gBcFx4JohKoli8nAJqGhczbIZQIWsynXCSA2mHQ6Don0YuHYY5HvEWsArMKs6by2Wthml9pkzeWwVilwI8KQPrdJEU/rI+xtsa/SfQwKQ1RRlKL1OLwpph6rAtUEbH5z+Xkm3LyFoOMtXwwkLBO1g8XgeSADa7COlzWqASHhKS0RU0rAwqJwyLrOGCUrD4XFKrtqusDRMefEVsfyaVR0ya17AK7GLrWXmsoxGioOKYXB4p0SjCA7ZyqpFNh3KTRG4LbZIf4vMKXa66hYKyh2ZJfNl3Poe6hhW9iCxDGkjBF6nAJwHi5EQT1hwrrBYYatOjkDM5I3FCxrJxmt9OaDs2oP2ZCqMd2jo6osL7SiJbcww1okCfu+WzNK5mXUjXjZafmAc4Lb2ZSzF1IOlafnzqIcaOw9qc0jnKhD6LMByu8Tbx3KhYiAt0ALDZ9uRqUJiAhm9UPagioTS15ZDkrqIEu0U0WBNW5+fbwDABflXjhMiXSxihDZK76x/I4+t7U3ARR1AtbRCR5mxR4TC1myarYhx7RaWcNkoU8t3KrXsMGOsqInoEJqCnA2F7x5Lz88m9m9FOMpu5LqxsksepfBuaquuAsiXYG5QT6vG3SCf1nVCMpSgGTqKHYqN/t4tCxN+sAjcvjopDvnSRiJcLOOW9fkm6LAnjw0sdryxe9Hyqnhetmyr5hSDNqGarLqxsxWkYxhEkydtyX6mz7P2aaerTVwNtENRT/Rfw6mzcJJu8BC4Bf55JEjnGsDV9bRGI1TdQZUgVVdZUUCyeSsKGJ7bOvfALD6viwsVEONOysOvBTxZTxtTekWDC5aSsixcR5weArcxEVt1qw1lxnVAW/ycSRhgU3Xhwmng1haE7BCuV2XSO/0AKn4QyMWKWKQaOYlrhArmVEL3mQOrwR0v7YX4PBlNxwT1nm+ExiwS2gfEVdCOti0afgTYluyR0my9ZVwbtNl17D1szPk6EsSF1FmYg00KuP0TCTVo1SY3GOo7RyL1QFFop2afMmzDrTgEF6eqbGSE93CBdphcT8tlUx/wehuR9V5UBL7i3rfEtPhQPQu08ffBqQoZrFZ7wHGWbGKNAxnfdeUB1dsMuCZoFTC0hGiHQnz4XGaMuN1jLOVVo8YIeMigRT/lk6Tig3bZIHcKW3pwdxZyxnTK0doDBHoJFM6erD1W9kFJwnFNC8HHcMLbB6NIa9LO5EP+obavAheE4HIvCvkpTKk/WUaqupY4ee6cAkSKrqjzOuzTkIkA1HRIw3iFaUHRi9K4lkRoRhEN2jjuKzpkM7cenpWVChnOFKuK1h/L08J0ngU+I11GMWUeu8pAi5eY/d7rYlahT/scEklbsf1JJNSmx3lv1dLyk1usMBqZsFpesEoFhhqcov1aEgJKmrktqQi8KT9Im0UYDMAeBC1tuLFVR/SNs/mcMsQBsV/WCEoTK2GAvvdDPHI6MfGuIVMcR4XvtRY/AHDJAyDTijBYrCZZU8oDYGATOCTE03BOHawJjDpoOERZuUyUORgJ0/Wa0qyGU2ikVU1rH/ckB9bapmT1TUeq8Xpa0L9evW/zFHZDcil5YGtuv5sYV64mSX6tPyPWMxqS5AFlzAIkU37USZJkWsYsEBi54ECwUiy1EA2XbRY7F0zH6NScNlkayYPVJ2J27QGaIOWRk7JrTB5Qs9UdvzpLwPCSbIBroBtEwAa4OVGc/O7rdjiENKAKfnRDRhcXJNs2JmJQG5FoaTKZZFlgIBOMaHFhtYlYn2lTaWIa4dijUdDSvnhwdBMBJyCEUqheAIWWcG25xh85tkAQMSijd4dpw4uMsknO8EPAZTWVZWJiMd78brAiVjyydboMaZxmaTLi1pLgWgcqI00LDGy2O4GvKV309nG9WGNXaWVp0ZY9DeCx72yMgraSTZ2M2hKOTpJUerSXPSi2a0QBIeQr47ZCt4yy/cxBsnUFQJywII/LwxK3k0BJkn8PU2ZEFrTAAx2NL7HGd1ttxEzDmRbd3znzoHS4x2RygYeYKnh5I3wXpkWM1ulXfk2LqBioQI2xAJ2yMwRudhfcvgHgjoR5fGJiZs32ipHWZYpZWvKAGAUyknnvINOG53Mwjy24EJAQh1w53RWEZXw/Zz7iJIp0eIiOfwY5WgbK/AOYRCvGtLT06KZSKSEa7TP6FoELdVE1TDUYQH/v8LkVWXAcfJxpYxtRiDdYkBJWKWiR90MtHs6ibUsmDdqsaRX5dEoTY8pq6Ms2iSdQBDJAKyJclWc0PYpAZrM0cy7xvQoWTcPybe78yMJHHcfQzt3XsslZ+F02Mg+DABQDp4CbeUbx0T71ThF+CM7Sfw5MxkxtJSdj4xOxvjQIoC0YaO5h6xS4w2382C4MtDzSjTOt5i+bWa0l9vAMk6ykEwwtMUOHlMCtoMjvR40IBwNyeTAM9hz00nOLkUswTE1oMnWs07WPn7dzmSx3GZkWnOaNBmx04+Wgpm2A1pJWbZbX8sDnhRTm9YwtCyj4OCAZojUnjY7ZYtGksI65MmwZ2xHg4igqgIvSMyDU9j4SEhpmTsRYgTaUBsVrbSas7IA6hu5DedoRphWar+VQWUsSpxypQNM7MizbNRiQrO02t9soWl1B0zJNrO8L6b1YeCIC5IimTZgR8oU+iLI5AS7RRqWBRgPsExPjBKKlbZTIG89lhtCpPl7XCk3cGNUmwtM7oa1ffGQ7yur3jjK7ZjU88dMTn0wWsdWD28eFBFD4r2RFkCvsYjJt2yELHEmUSsAFxmqlnhrbbTqnqLCiCNvQmAnVqYlHLnDuyBG1LV6xeJMlyeF0s4XgZNPKsZKBN5gsGt1c0RMn8zSS7wk4/DWAWFQ7ePRjNRTLAzftnSWk1l8RG4+QCLQj0kBEa8HA+ccM3OAl5Rqkv4ayBw15AItBUOcMlk+TuYrBhTvyYz/jdn7wR51bHojQxH+EYyl1n4oEzUcO/tGwR79BHEyDb5NBWr+mtsf7hdv/8/vcubtuc9PuWTsPLB6igTtQzBNsO764kAu45nSK5Sj+3GuP6P2IaM/U2l+24WyFG2ItAWttVO73O+7Yle91O0990eCwHl4mLbD/p59zp3/5ajedPSl0KdKk4XeC4AQ7lhdIQhqti0DRATu/333tkbkxMWuzxopYjHV9pi29hsciGZMkaml5EsshcDf2xP0vf86d/vfjwGXkC8d9BXkAIxxzmFD2iIOTd4Fxqw6TtkgPEpqWd6ABWlF7AAGuDCAmibV1fIHhELgPAnAx02YJUse9tzwdyAx8/jU1OeQy29utcEqQSJ1zryHAlbqPAHZM2+QHoMUBJLo7TNsqmjkE7vaAe+ZU4hQJ3Phzn2nR2M6/G1uxY4G1FATJd/PMg4/ANSZEDLhi2bFRLRU6S9JW0bmwUbT1a9ledUpw72LWuL92qHE3gG+QCkHjEuDW2S+oBBiYiJX74TIumT8C0jKPstLj78+95iiXERGsheYz0+Zbw8+t0kSRPVCgLS6MnUWfvmIAfmbcqw6BuwFuHQcuZzRddjk+gR5jWj03wsVE5b0J9LGdArh6GZezcaMAPDNqcgOmh1i8seVBdAoiN+SoUIc5BO4mmA33RuBeo7IKxt438T40EWvIA2Ns65gT/crkCcUc07iUcSVwx8O7jiujx4RWT1dgz6ZCM9igcX/N7TztMB22LoI1cKuuZHMn40gpLoDXWFxIDcd12gmwoSGa7Py51xyb9NfHI413GTCSdnw9ERyR0/un05Ql3LywRJ9XRkM/P152JAH3heuO29/4+2bgnvrla5wLeVxRy5HHQuWjENOOTMK0NIi4MWp4W9F3GaQCB67MHsQKLBTes7vUBunMQ3vHadMxSg4GvXsRvhcbGfcQuOt6YAZumJyhCfpaoMVkFtuoQM9rm+E19Z7cnPk3ALiV4eCsnjKh6NhK1f1EBkDAmx6fvPsQuOvitdxXgBvSYRJUePLMJeGaK2I5IjP8kMhagAdIa5mW78696li5fZWUVepA4MVVmTZLi+pBoxVi5NyDxSHjborcANxfmidnBLhoPqGZMr0abbw0AE8Ij0fa/AcqCfkzGL5TzYzPwB2bSeIwMF7lxfXqaoCvVVxx08cipMOOHEqFtfEbgXttzCpkxh2aiM2vHNluwyf3OLKiOYwBXHLSZwKuqMlU2obYhnQMZgGgxybAs9NNbLHOR0IXgIduJeAeaty1cev2v/x5zrhxQPPsgmjStD8s/H297IG9YVYCV8uDQoxkQcyffdV5BKbjB3WsxpYo84Bmp8ZELK3E1SGa17oX7ryrfnWzydlcDjmBU6bXx8L6d9KZB30KgVGB1chbWD4L3LCz4/a/+N/dqf9wQ5QKa+9a6JwHkV4NU16S3Y0a8GAaMUwVuCZT8swAZNnsp3lCxewUwdj8hhjeo8THqbw4gTsw7q9uJBX2P/NBt//Hdzvnd8TIGlkUGG2MyJGf2FhlzMerBozBKDeWzSmvYvATs3H5fL9w04nvuP2v/qFzB7KemYd4u2BmbAkYnoBEGw1wU5JK8NO4ziXgdlbESKfXYloCbLyF3GbajF713i0A99ydP+v2Pv0O5xdHe6dVJjvSyJEmisBJNTMm52WHKKM+06S7dggyixaF3yR8s6+kCPBmgBR2nyOXdNogB8TGxpWLwIk9ObbY5lRAlgWwgWXtiZ4/+6rz4bkKVe3wF+vMA2ScWtKWjISFuRVmyHYaybQZKNsA7kfe4Pbv+RXnFkcLVzFi6hQSxUhisaI16UhDM3Y2rdKbdGDtzxw0xqwbCSTbjh7ZBByts/0+NgVMxKztU+Raf/aV5xtTMdn5uBrGVEFrIrYFeWB++G5+71wddtV7NpIK5wJw35mAK8v3DEZMA58nG2nRT1yMQBsHqSCxOPTIZKfBnErLcuCpw5hDA0bqaRv73xRiBPhauGD3oolYm2kLb2Hgoh2kwjtaTKM+Kd+fiMUBHWDa7DlzrcLVmwN3LwGXj//IwBrn05rhr2r9qi7QbmM0mPF3+S8l6iEQgN/1o4hk2SAXmAhCS/v2xgC52qo0s3g2IQ2oaTURAMaNg1ZDEjr+xy5xK/dZIR7+nuhF677S1awDF9sB7t3vdG5nlgrFl9WuYXnuQf5ZhypsF8p69SMo48Dg77EkSA4F8f9nabaSQ5Ybx9tGEbhZygsxLRkTFuoXTgBXG0WHGkvTJb3aK5hRo03ytOYZXgBUs8a9es4qvEB57+gvzn3oDW7v0xS4IzWnKGwnPrRCaJa1pX8UGIjtKr9C0LJBRPcLSYePouJba8qLRqSLiL6pPbimRWIK4Ccv46LzglURV2wfAS7WtKU/JhOSgbS+hlhoWDaa7Etqnm0LALU14L7LuZ35bAbLIanhJSAIyw0fgTrCZhq4kTmlHTDo9WmSqG9Ivo20zXiWkofSmQ09mw/0s3aRM8+tkTkBV4NWV/J0BrZ4HfZGrYdEOqkl2hGo5rLGq+/YjHE/8ga3d/e7yuRMM7UuYuZ6UYQy9gCuac1zvBh7WkzbYPSq6dipjkUi9E6DDG1GB093okjxWS/OcEPhHWMny5m0uFDmrVpPE8Dmpb2zrzwe7s8J8OLZTLwAMOa/q3pK5Mn8d7HB/GuN8L2g1rd0KkzONgRukAoIuDKKWBMxzD5kBFJN81geFIXakvJCMgR8kagMSwyoA3o9AVd5bT/Er6tpS1daEzHm0DJn7mepcLzkcfGBxRZoeceK90jW0TMY8DmmTigjMqX+544776o73JF/tInG/VdR44Y8bp6Pyn41Io2haQNwm3laBCokQyR4CJuRd8txi5UFDVnB0D1amogJSZlgRtN8fHD4Q4NpG4sLBULhGXihpwAXAg/qWq6rasPHmTY0rCUNwt+VHha3bB243NDJ8NDvOkxWl2/HQaHfg0Ar5Yd1mLU8twAAiICCk+34Mm6RI+UBtM0GaAcWFyo+xLluJAL7s69IUmHVCvho6UQufdCqrSGmR9qGK4M7/8diS8Cd87ghHYZDIyO2sp9JOxVTVi1Q2E6biRpuhwK7B4ydKWPAG8vTctaMmjL+DoIWEh1dc7E+jRvsGRWWsJ0ixvQLf+YVxye9ImaHbl4sI/aWQaaMQ1oOVG4yrWaZ2Bu5fy3uOTvv6tu3IBVmjXuEgSWHXrL4l3CJQZG7FM0vWUKEdzXiI0X0KMqNkIUI18X266W8eqfLNORBdMrBxQW9/y05CsGOP/OKC+qP2GPqoKHywrU0LdI/NijCWEvA+53tAPeeOR12lEoXcEJ5Z+8ca9s4KKJTgu8tIOcmYwPly0j2YF15kDSz/owtGUcdQUsE8Uke6HYjohqzHwCuwbbprYoBqb6BFpWrYsZkpxo+BwMSP9LginMVtsG4u/e8y/mdo8kxrLZhRwunby9RPW/HCQsJH6nzGHXSjwQFYe4MdvV+zszxMrmZjLRt/tMcbZpRsO7EXUEe1JVXrmmzr4L5hJ6ItTIqFbjaY9TDY/+6wCtQDl8gJ4CPf2D3hzkorBBS+5zEe7fCuD/ndu95p/M7x0S9MAkjFpNNS7d4zJPdzt9/bu3T7KOwLh2Twf6f/r47+F/3haL4pKdY/MrprCpFqGSa3M6jv98decpzNDbRGJEFgtAav3DLb3/d7f3R7zq3v8+iKm3EWPUb719AZ1oNaxaoZ2UbXggmYooI63v8mZtnqWCAkcyelUhuMi0qlgGgbzgLazNcgNiGVJiB++6kcQVmgvUbNRkH++7YpVe48696q3MLq5JKPFP8eOZDv+B2P/HuWiuBpJp1ntbBgTv67Be7C69/i3M7qKa2/e75r/tf+oI7+bYbnIM7IKwNBCgSVPwwnGgnVuWusZVGbXMjEvgzN18IAjzXHqsxLZcG+Ulzco+ZEoKWh7bmh/22xrgWcO0JaiCKg3139NIr3PFNgHvnL7jdT94RMiT1H9J98XcsVG8FuJ93J992o3Nye3oam6YmDY0BTDs3NLCttB+aTFbQVvLV/VdFTnOMJsCt+kM1qK17uW8v2qtweQigHqahEL0zh9RlYMnzrn73hlkFi3E7Kbm57csDd/TSl2wZuEqjJq0IjrNaLt2xZ7/YXXD9bRsw7ufdyV+8MTJu2eWbVYsBPBQVEgCqRGg7fdXUekVMbEXRGj3PgCDjqu81IG+RjYvyIDhiU+y3S//aEwWiBefNktfM6bCf6MdE44qzd/6c25ulwlxkk/91qvZL17YOXA3aktaksixT0/JgY+Dufenz7tTMuEQqzJo2t4RzC2VCDcxqlzHQsu/NjdieXBNwVoFbG1YAaIh8rImTrl0RtPFd8pwyoIdpYXM6V+H8DYF77s7EuAW4/bLG8g2xgz139LJZKrxlfY175791u5+4vTqOsJ0KStSpZscJGndzxp13+YZV2qGUGp4PBWnQ+E6IzbJ56UFKBPAeUhdTgFuS7ubhutb+MKJTVi1NHP4ehJvcNH/KkhLjwm0HuDNwZo3ZTmGpAvCDvenopVf441dvCbggBFcCSY5MkbwVxv2CO/WLN6ZjRkcKgYxJPNS0gnxK20HBzFSSOlrrV/FbF9YK47ZOzGvSONq5gNhyYP9aaKBhGPSpoa1JhdtxVoHurxNGDyYJk2tz7OsAAB/vSURBVLOXuO0CN/afARZOYn18/49sxrh7X4rAnaXCqlvIWfYgTci4IpMMamQOGDABdkQFXH6vP3PTQ8J/K+8ugM3/oTUte6dmjPhIAMbxw/FQnWjacBiAO0/ONtW4beDmEKc02ZaAe+4TdzifsgqqOi9EgTpnrsCYgXvgjv7I5RtJhQxcfSCI1NvAoUJjVpMHHKMD2YPUYVjuGYBL8ixjZ4jFnQsFq+ZMEzeueE1TWiQJIq4pTfWzVNgUuG+Mk7NQq1AdlEwyZ7fzcCKxJeDufuIONy129A5qsxY5te5BAy4GLZNpiWFZDUkGsrqwSJDQ8Dj2CBeSbWuUVlp/ttbpxLhwwhXuIIdDhJeinQtISAPaR3uKoDzQK0nqPIfFgwFcvn08GszQfvPkbAtSYWZc7jjJbnq0+KpjAO4sFd68QTrsCyEdNp09neBmT5BUc3A9bVwJJUTIC2YGACtIkL+3gjkBdyTdRQYxOT37Liz1tMZEJ1xmMnRhWRIfa5gqr5j/ujXgzlIhTc5KDUB+U6PgYzlr3J/eWOMOAldvbJyzChsCN0iFtybgijwuIrIwbO0KrwhbMidQErTnkOnvWjZxh/ZnbnooOLbK8gyZ8mrk7EgDV9K0XAixmWSEU92efv6179pQ477R7d1N0lEJryXlxZxRhLitAfc9fOVMTcZQfncG0NIdffbl7sIb1mfcCNybuufjlqE0vufMCEVEqQqD7jaihHFAVEBe+NM3PZT7AAvd6XjJ1Jj2woIV4kAtLayN0BOx5jljs8bdMnCzCIOaVkaRWeNeNmcVbtsgj/smFxk3Lfm2zqbN6IjyLWUVtgxc5TTVc+OhlpKokFPpjY2McMojUZSv72CA52QWoo8ALvKKGi5tabBd0DIBb0mLLQF3lzJuBgViWhHi5lqFY5sC94MUuEb0YrGWSJeQDtsicK1PmI7kaMsYEaxIgDL7YdByojIiTWLfClzzXIFYexAKpUyPLNICa9NWo8XWaAXaYhTR2ZBVeKc78vQXGDWxCn3qF2c/9PNu9573kHQU0LQgdIdfHextEbg0q5GbifOgpRNbA+7N9HxcMn5pKmIc86kKX6BE6KS8yNhq6du+NwIXApJQPllNYxNGqjkJLAZPdIT7+bEexmFq5wee5fwjH08OZ7bCTz2SiKJ3ef8fuYNvfCWFwLHK+zxgoTrs2T/ljr/830SpACcdDedZLNzZD93mzt39PlEdJvpqRYEHB7iswaY8KExGL+8WzOBa7snn3WDkYf19c/70jQ9TJs8aL8CgtQQsdJ+tS1AY5Cmv6Opy/1WjdmC+YVq6sAuBnQkb+m9W/nPy3zHOh0XSR0wapsktHn6xWzz2B5PBUR/xlp/c14Nv/E+3/NZfiAHtMG0miwLcf7d2OmyenJ18682yHjcCqdQd0H4hYjDkgULViDwgTtuM0s5h4M5LrIVarQHBQr28r/wHuF+xSAVFeS0I0ZwOeEhlK4MwgtR2FNk4pUNJupqW31suD1tn5AilvliGp3aZdz6UMuU2KGoT03VbAu6pt97sptOnnMulIDMXhNEfmYjNrZJVgQZexNYkKAnNRRcWzmOqmDJu2GqjcpmlIVX/GFVEbKwa8kOsL/OcdQvwbEeGNlBse1sbccdCwG3v5I0mJO+WABU/g3DWnp3nMYISgbRtTseFydlmjHvqLTe7aS4kX+Ro58O2myI/ZX/LH7g0iBaZK8wkCyBbrfE1SYZLAdyyKmbStAWKKHrCbU3gSS1Iwm+hQQEMaDgRtoutbNCGx4+cLsNHrOSRi0Pn9nQZNQ0kG8d+2aSbyEGA9F5JBFsE7vLM6Tj0wymvyLQQ3K0QX8IikITMYREG+OnvgXEZ05bWIMq3j/WpTGaFCg7arPO4g7ZFeQQfB20FFAItZkedp+2UNBZ/bLN5LcgxnE+xEWpfo7yQ3r8/55G3wbivcMszJ/mh2tLZGEJr7UHdrNifTEWUA8IJg9o/LkrWCvvTN/4tHuFb+tDwJh5+EXA7tQeQxfSg4vNX03UtUORPcaDySKPgw1xybEUVYTtuWIMIGNMMgnYOyftzHvlFG0uFk295RZQKQ5qWfCTRlGW5Q9LJUZTsEEF6lC5wD1IhAreGUmDgTUsTxWDzAe3sps0mhTXDuWf2RGLdc7zyE2Nbx5jW3Gbf1X2tXcIGAAJwZ8Z902ZZhRWBq+RBq28getfLB2RTmtNoTkrAfXBWxDTLRpiBjX9wCZgP2Nhp19rbY6cNYMBdqsbEwRogoemVUw4yGYvO5YeGNJuBe+nl7sIbtw1c5KTcfpHpLEnIJ2M4cqH79fNaW4n8qRuyVLBZi0646MCYX8UJHQOdRYPY1X0MSPnqmK5p3FvCCARu47yEpr4jjhFFespzG4PYqz0IjRxZrROyaX7trHEvvdw9ZKvARRmAdfO087hlIxGjhgpndu4UlinWCefpUf7UDQ8Hw6+32kTT1Y41WZrkgWvTpcbphIrQcMurka6Vnj7OtDGixfvbkz09sLgQSAMtRxvtbBS4Y5ovtHcrwL3XBY0753HpsRcFEegAv4GJWJJ1Cp+43JWhOyaR+1EPALc/O69s1piIJX1DNWLpCA4zbLUrhwndeStMcaeK9wk2E5OnAiZJAErTin6mgWX61wzvQr4U3bfaEnO5LdZGuWl/PtfhhZsx7hcTcOmBIAC0tfed3djFLiPRmzt3G1MKk9MgcEd1aUp5FbShIzQbbFRGx2LbwRAPswdtXcXDToNVBPhVuGrJAwIKrWn7bJuLnIJ5Z+BetiFwv3SvO3kbyirIxQUEWDGOsW98S1fTmTm4cQTHhBHezKTCUPZglGUB5ff27TfrIpA8qMbralriFONMa7ElYfciLwZmySZwkbYUAyvD7IMGXJny6kc4Kn/gbmHm3Qg/0XZas9p2qcCNoCJ6A7GsxZYogzBSMENYJtCI8ZGQrD5Vz7g8MPeHgfsMI8X+lz9iTZt/S+ZncpZtHB00lt1QOVWk97cB3CIV8p4zuRo2NvunCwt1REc20/Lns3qTKteSmTngE3DlZAwclQ71YZoACRT0vU47gK1pG2F7marDMjHmdhjfHGPNbNQf11qKaCwO0NGJF2CWhsbO1VjxXZbEEoXkz7ncPeTmDbbuBOC+Mi1AiOyBOd5AvxLD1v8cm8TJoaPRsHUclz91wyPA19NX1LTp7XqyMhY+zexB03gLt/P3nuUWj3qiqsfVbNrKO1K1icJY7yw0cr/Z3ngNGlTK3mzQtAhOD8mctnRHvv8fuvN+9PK1tw7tAeAW3+8dElO8mcum0uyhVOBIsQ0niowxf+r6R5Bxrg9KGx7SL+SA6lpauL0dpz/YKALQVrliHqocP15y/rXvcEef8eMEuNZo///6+zknip1tpMczcB8gjFvm1D3QBS9EgEUTTD4PiVegKjIRyTAJhM8czM8gwK1hkdODMRlT4UHOAC2D5t9bjcfF19Qxwgx0seOOX/ef3NGnP39kjA6vARYowD09H3qX9G1vAl1ChwRuf4IZNVej9LExh4mvjRGcABeB1gKenIiN3is7ZkzEWjpwbnzu3CFwN3bGCtwzjT2FGJDG5Nb8nkQsfB2v16WdK6qEOJU/df338DlL+cmQB0ysjeZpqdbtZg6KzMqarzY8d2feObBwx69/xyHjbgDfCNxX15WzFuMJQuGXdspRy8fJwcQut589kF+HahYKcMt9kPHQ/rCkVZjhOnvEkr7hE5X0gEaICsAtuZKUZzwE7gaQjbcG4N726phVYJkYnBExT3TshPiYo1XPjCnDwkpC45beYUkZgNsG7fyEWgSeZ3XQak1RbyWZBzRtQTrZanMI3O0CNwIrwUjOVxBJjeZ4O9+DMCN8axK3cP5klgoDTFsEsjRZR5fm7e9aFyUvszw2r6aEIxNFwcchcB8M4Or9dDwnzjdSDkymmh/1E7KTqoaQ4VBEWHPN/uR1WeOi8BAuzJE6PXcke0BmnKnaB8sDOzyE64s2AitOM3Cvm9Nhh1mFdRFcpEKpDuMTsUi/YvKd5V5Dk4ZLzOq+fvbBXowqkX/yJ697JCBClKcFIOukTnKJGvWkYuSBe+MbQWndfO9iBu7bD4G7Lmqpxj192ihr7JcX2vn7+cSLxlctgSwpMlQhkhBX+hsGLqmuMjVtL9/XqqfV0qJoK5pBiAlxWZqYPPYQuBtANt4aGfc1sB63eeAgmTixRoTBG015oWiL7tWgDXdqxm1sIS/UiXK86+ZpReojhRnItLRO9hC4WwDuH8asQpYKhFBUuktpPYyBsXMVNGj1uXQzYOf98hK48b0EuPZO3Mh86V+Lac2Om/fy4vFinIHdrmFydigVNkHv3hclcOVqGAFYayKW/ja+L1Bo6QCwsXWD3F8O3NIAROP9ukxW6Kws2ikCL4YZAG1oXlqAeMbzNhm7v9H3BuC+ec7jzmWNqxe8yPmKwnY4WD/pXJH2yj+GSrgBTcuW/CvjysM6UFmjLQ/Ce9MXCWHKC6bLonOklEVKIIxuZ5lv3UmMewjcdb1vBu6JN7/aOQjcNN4dps27eHNMZYeENOTFKhMxtO3fn7z2UbF2wUpvNEBX1IN5jb24QCdh8TnDoI2zVb9wFwSpcAjczYD7mnIgSJWE/ZRVwGRvi7qBKRu0AgcN6RmBW3K1eMWEG0auIxuyIkposB+j5gXjQf3W2bJop3ES5uHR3h37x1e6nSc9DZyYWFsMI4BigtbQo0hDrjdf0LlPvLI4cvl9inrm81GbV3inX7j9//1Vd/Y33++m3V3yMMS0+rm5UgtiwyLBzGOQ6FT2IJlE6u48OYvAxVvBe3WZ4ECN0JEeS5cjkcaZFgt/ckynFdIMbVXb2dDUJaQYgOjaZ5UyPvAOfUhfncwa381lDjCSsmShdhC0LaYtDcA2w6fao5SX4SxpTBLjVhasHlQYLzelLAdGXYruyUrHSJUkULMshZWnVYRinPAnAbv2hk8UbdBhHbbWz85Qm8RCrmIQpd0YGOV7UPQiX0EKl4/pUnMi1tKzaexKmGfjI9+7yvjb8oAHRm5L/8C1fycMNWvzgKfKWR4AvIBeAnprGRcydWlw9JcsQWqvOCAAw/L+dfZClft72Q3ECLmKDYCuGYmSqXofCmGDJPaIhed3nCq2obC2crBGiI9OKckKOTtxIIIALC2a516QI/bpe1IxeQFuk+IZ2o3UyUCVVxMU7WqjYSMn8Ofrc8ubmqwYuKG3ufuDY4NybYX8poE2ugwmwfTLRjpKvXtkN25+S2FDnTPXJJADcRFJNfUzuoVcO23SoqLbTdCGHsOT0ZPzBMYtT+zWD4A0Wbm5X0wcL0XhV3upmcxuhbOWntUTxTqQPZbtMVFiI9W0DIyATJzHLhFEaVZiE/Jg9n0OOjfp2qUCipFATx6UXSfCASm0KQYEPNdg2kju1KnyL4gNI3DbIZoc1oAFdy97UHWIBC3SZMjAeBCZRiRM25cGIJxB8LbbV8ZuKC3Ebcfw0vrMKGHbsQOpcd/GisDB+LZ2+7YcepoZE+3kbDOtiqyl/7xt/oFrMuMaHpVutLeQG3oujWr3fFrGJo0Dz1i4lNKlhkXNeiOatqdnRdilrNLdxo21cHhEAqxyNPb8GqWKmjOjnLBLKnipcon0A4YHfn/zIG1jPMpjIRl25YFm2hTNBJE7/8A1F4Mu0A5IQU4eMTMNt3rNPLCnji4uoHc1WI9IgzHACjYKN422TTNZ6/zWGu/sKBW/I0b/YYek8qACo00YuHjfdnjZEnQKOLtGyLISfQygWcUy7D50OqchXwFwA0OFlTyac1Ne29XDuUkjmtZyDgO0gnrW0sPBwAi0CaCNMBj+1DrzoTJibmlRS6FH6SMh0NnYSNZJWOWHAflCSkohm7dCvLkiJhwFMKpdq4JLE3larvv8aMv0XgFcoi9N3TaWdomdGGWzkYJlwnjJ8Guddg21rGA9hShp1JEMALGlpDOlaS0mlCmvEdnTOenSiJDFX/KsnaVIUfvqPsfqazjXrKKKkIfx7zrKS7MFvihZBSoVClgNBoThgQ9q1rRmPa0AhQYf8jwNWjOP3FvNCr1HTsWYLDElCPOwQF4OrL4vPBDmaVV/p/q1PLptCjGtHPD4s8nkHabNk2x+f5vh87W4ymtuSv7yH0+X1qBpSB4gYPOhiPMd/oFrHl0vWW3XQnKI2jH+Lsq2yhNDu8eq7PHgFPzVsGzWRZRLWlGAi63ctxrq6945khi326bat+I3xCog4oJBs0CbpKwUoYKQrrfbrH0kUsEAbx+ahAlbpTsZBqwon3CZ+8aBS8IvoujxlNlIyqt2ooLdCpc6TGvQosUP9LyR7MEoy7ZBy9o4mO6i0qrYBQJvhGkHGDqFaO0U6Pn8d7V9KLdv1x4Qh4yRv6TMjHrt1H9JwJVxm2wLBpPuww/3rjbRWSmcEYkC70NhhWkmJA2yyxuhqjCCpemlfBGSKXs/BG26l7e7hFXmzEN9Q9KuHeJrFBqRFlj64AnqGhOxVo2FOPC5sO4sFXDFTnsJN8X69P8MYIwUvMBGE6OX2DdUZKO1aRl4EAkUKAD4WqG2OlSRFJxpUXi0IovczTwAPMFG8Q5iJ9Y/g3zUSeDyvei+egAdj84StMiZAVEwsZoB0d506R+4+jHhNjWGAyFqlcWF+I5sWrAOTVi1yM1STs9BW7A8NBFbLRLQd0cYGA5TjKYHJ7bPp7QXHdoUDpkQBSmv3kocfzfW3C2nJJqRj33oS0yGln/1PzNLmSWmtKsEP9mrY5OovaztXDHKtaJrAO5KoE0fr4olZauBYizMa4/nHYdhVqRTwjX6OPtsuB7T9pwWOFkBfH6HtbgA5AH/1YA2Je/v34s0ewMUzKnKvRGz4X87KS9hG9i+YizM5vYnFeq7/YnEuIzyjeR69bh89Xi9KvQ6jWRwYLAd4hArVO2G5Mto+G2cd9UDbSt7oPoL5AGyidLrnAVjnwf6BhcXkHTRNsfFMgAHUB6OMm0FZjUDllYAuI0kN9NDI9mD+NLciLi1jRReIBC0PHY4hIa2ZV+p+B5e7QM6jMVUNLC19oCRgAJd/Kt50Am7WbeD6gI1uAz0oI2t/rfunQtmwuG2vGetZVyTafPOX4O02gxdHaACN4dHFiq0J8cHj6SV8ApOWEk2NFQ1DDvSPw50oy6Cecfgd3vRztFWlRt/B2A8xbTZkJIJQS2tKrkUoAPSpUaw/kdCYqTkJMJY2nDKONaWdLFTXuxxQ5q2UevLQnW1iwYu8vgm06bOKQ8ywhnybK4j9P63XmV/eaZRFyFOHIyjIcGxWlqIRBE3rZCnpWGgAKNhu+gwDWnAUaIK3MOjW5NYk2mj45knJrIP9RrSQmLJkEH1cEN6Q9t5/YmrHwu+uiPYsgkMrEEboSK1jmuXOKDohHNDwCsmGK+LoObJA6sCjXo+b0cBbm8ZV9iO2iW+UzKm1nTr1tKaac6OHi5jAYGW7Fz+xp2q2pHay1pcWD0K5LEDwK0PC40wQWtV9MuVFOQ5PARV+wCQ9lJe4eYx6aJZdmQSRtpKhXMALP+bFX5nPZt7phzaYrz0ewpaBQqTLfO4rTYWNYokDLDnr1lLC48oWC+K0MDsT1z1WOFXtdHRUKOgiIPIoz7SR5hRIOP1QmgIg6PtE4zZ+kq3ofmyt+McrdCDpEMZuNU2I1VeiI0szTkie9S9Kl1YzK1OJWqDlvWLhbNGtBRjq0jAZPT4TAHcCrT6XCN7QAaXhnkOQAlcBdry0eJ8XzwkZORbEtlCxooYawgIZ0O6DxveXFxQepO3DYfudkQaZOiMnWiU1m7cFkuPTMSgZpbRBxEWQfSwZscROHQ2Mi6XB/E06YZm7IGicYJNNGxRKoMrdmhw12daXR2FmMwA7cBELKJI1tKO1THTnQv9KKSjSO5bm0C0/MEZlb48yPKI+cNgLTeTiL3oKivEMnDDfeXMA6QbLS8aKQLnTJvDAjVu/O9+agfX0iaF0tPDvS8agvBU5QHcQp6YTh4XhTTtWN9wcXznKKs0dmZtc4NlC49AsJGJWBgg4SiWxlcgrHUvhpSs8tp4pjzbzp+46pKI2fIyYxm3AQoVzpBGJBetVYeL9SwEDpqE5ZOyNYvJAeHRh8qg/KHoGvSkM4O6gzwQLUZpRCBcoM2lT8jHkE+PMyZDY8FqU8K9ot5hlGll9snQtC1p0LBLQbOqW/BBKuy5yR+J/VsNtNqx2qmdzLRUv+dQAyoqRE7SOqwjhb2WdjM/oEKBZ0mDOWkhC2aUVmeZjbYu5WE6a1JoS0r3xWjCscKNqO2N+UUB7np1B2U+wirR1gCt6p98hlkhtu9PXPW4b7tpekRT0zKkNcIF8h7GtMjIIzNsJF0QYPXzYzgxjFqkifH3/pFIOfIV4Cq2Y4wn2iekCTNfb3m6PHcctKSxkSda8kA8H0bVAjwLtLy/MdLSVrD5jviDPMaUveM7/q+vvOSPvVs8BbKgYXTaibJo0GC8ONlDE6yG0Zk3GsU8LO6DZ/U+WWSyVXw5XhEjg0HkFQcFmg+g/sdnKUD1loDTuJgLEx0mGzpdxgSu7EefaSuMUKQyMJBsC4nATX/iT1x5ya9PbvFPOHA1C+Iw3xggAWRFxpss4w5M4rqrRg0mDm1dhuR9Z39ZjAR9aQAijZglF/t3J5irTGKN6KgGo69p4fg39ojRgvaIJp8OLGQCGy9RJ80OAR/Gxt3lT1z5fbdObnqzc24nGs8GYx98yKMQ23aYdorZ3FW2tzOSaR7rn9+NBrWeeaAjEGZaFZTW3tHRtn3tHxofdG/pXyX0FGH4OKI8eLxFRgK2JG9Fq1aUUwDSUWggihwsJ/cv/Hdf/vhnOr/8qHPu4hgf5ek0cjWsYaTc01KUk79LSJiLe2m6gwLeAm1nwNLIdpmWHLNJwVls2tuNG2yEKrySXTqSiebM6/uxw4PsCGcuTjRCI0rHRPZr11PX4VxNHkA93LJLrv6DbMGwMV/5zQO38yL/zZf+gwuPH/nrDzvnf0Ks1zIaV1oDNYTpkrZ2yTgrnzRkz1uvYGbsXIDarvzKIJUhYC0WHFlcwJqe+DYZprYmDu3s1SIb1VosxFsSofzekBalpfZWm6ipetucgGRKztfbfl9OsHHut/z+4qdDS7/78u99qXP+Pc65Yxz0sSNDoE254AACMRELv1tb044zLawuY6Gbgza2Nf8z9ogpLSCWcY1CccaWBBiQjdQ7BIBSWM6/tccDkYVle1lPq+/lZx5YoENLzOJawzGi5VEtLopA4Xf7y+Xy2ks+8bvvCz+duO5Jj5x2z/5X5/xl5WEEwbbXElAVFEiwDyx1lo6tybRVV/FwaUx0FPHMxhs6gC5OxBjghx3SIAEGWgC8BFrYZnBvNUBiP4tljfcqp8g4MBh/nJCAY8C5iARt/dk7/wd+37/w4k996i/Lb7/7su/7p5N3t3vnLmBbnLmO0uHNMF69cN08rel1LCjEMArYoLPSZ8sDFOLn5wNduwJo+8BDfYjRqzoKsom+rxBNC7Qj42bZlkYpJg9J9Oo45CDgaQXb6YMDd+3jPnnPh1iPv37dYy64cPfYrzjnXtY3MtZk6r6B1I5Re4DBSIuByASQIVmFbhQCE2u2CmaE5t460ypjaYdpzrAb4Xc8T8ulU462SkJpQJQQr1ROpwA/v6OpaUHfls5/cLFz/vWP+djH5s9gcjF64p8/8UnLxXJG9FOZZGANbzMhCzWtwWnJgx6TldoKCUo7zJSIlw6fy8u4ZmgMI1KlQR2gxrI29SCyvq4Y07Jnjm6TONpARj0hy1jfeCERkU60YCZlqFuTqdBhrJlRJVlvgSk0hETGYXKMfb/3YOFe8rjfuvvPBKdXi//VlY9/rp+m9zq3eEL4bRO03GOLPQeAZzItrgnlH97oz7Dh+n3oymD2II64XFxQg5mAgR1GsfTAwgkFhXKqBuCH5IHB0uVe5gHtmmgOPBSBgYQhwBVREnhqtqn/2r7zV37vb3/qM/Qe6FLffdkTnz/56e1uchG81OMZmEX6I4xU6TDxdjmwjVpaajz53sJIwp8kI4msBgPQQD1tbAKo9OpJnzSaYwdN48kKH1Bh+x7wFI3JzIEGUxxO2pb26TJqeMzic/7MzLgQ8AJT5B33T37nlZd8/JMfl3axYoH7q5c94TneTb/knH8qWk0bK020JhNrFIH3wowxqNUIMXOg9JsaCSYPkr2xpmfGFO9XA9Rhy1iW2JM+OQbWsynCYyGTCdACeaGYlhCDciBgf72ShhyDE5mKIsz+DOz3Huy4Wx73sbt/DzmzCdz54u++/Ik/sJzcG72bLnfOX0B2Log1/E7Kiw1ae7WmNrKChXYWAg+AgodplO4iRi73V2nQ1qVigIDTMFA0QFsYT7Fley5RSQp981fWHuhJWPwNjZhGuE+AVc0bmoQlO1kH62HQnnaT//WlX77pkt/+9FdwBMKFnOzasLK2OPmCaXKvnpz/Ye/cUcbspm6JhRWZIzbZiavDGQqhIPTmy1bMHnBjDUzGoESAQNDnma37OabmMmmZiCX/1cDlQ29p1M7ignbIzBfRhKqQSIwRd/hdN/nPueX0Tnfs+G/k7MHawM03PvDSv/uofeee57z/Se+mp03O/W3n/E63AJywmQIE8wAGxmKAsXwfN0h5ZW8ixt4PFhd65YUEsCoSaIrSS+jDs3YOvPCuXi0t6xvhD1R0bk2m4a6KyqIqOrIBbh8TmoB94Jz/lp/856Zp+V+Wx93vXPLRT3/bAiv9fVMqoAcEBvYP/NByWlzm3OJZzk1Pds5f7Nx0kXPuCF7qNHbiio6q9yWDKr0IBkXei2sPLI9HW25GmXbFLfmFiRIALBsA+VG6rYEGvrWAn6/8ySxNRCwtnmmtpqVFk8i4lZC8c3uTcw+45fQN5xdfnvzyD5bTkc8e3Xf3XfypT50aAWy+5v8BUrIHNHvQF7oAAAAASUVORK5CYII=", }, typography: { "fontFamily": `"Roboto", "Helvetica", "Arial", sans-serif`, diff --git a/frontend/src/views/Admin.jsx b/frontend/src/views/Admin.jsx index a506a529..067e8d4b 100644 --- a/frontend/src/views/Admin.jsx +++ b/frontend/src/views/Admin.jsx @@ -4,7 +4,7 @@ import { makeStyles } from '@material-ui/styles'; import { useTheme } from '@material-ui/core/styles'; import {Link} from 'react-router-dom'; -import {Paper, Card, Tooltip, FormControlLabel, Typography, Switch, Select, MenuItem, Divider, TextField, Button, Tabs, Tab, Grid, List, ListItem, ListItemText, ListItemAvatar, ListItemSecondaryAction, IconButton, Avatar, Zoom, Dialog, DialogTitle, DialogActions, DialogContent, CircularProgress } from '@material-ui/core'; +import {FormControl, InputLabel, Paper, Card, Tooltip, FormControlLabel, Typography, Switch, Select, MenuItem, Divider, TextField, Button, Tabs, Tab, Grid, List, ListItem, ListItemText, ListItemAvatar, ListItemSecondaryAction, IconButton, Avatar, Zoom, Dialog, DialogTitle, DialogActions, DialogContent, CircularProgress } from '@material-ui/core'; import {Edit as EditIcon, FileCopy as FileCopyIcon, Publish as PublishIcon, SelectAll as SelectAllIcon, OpenInNew as OpenInNewIcon, CloudDownload as CloudDownloadIcon, Description as DescriptionIcon, Polymer as PolymerIcon, CheckCircle as CheckCircleIcon, Close as CloseIcon, Apps as AppsIcon, Image as ImageIcon, Delete as DeleteIcon, Cached as CachedIcon, AccessibilityNew as AccessibilityNewIcon, Lock as LockIcon, Eco as EcoIcon, Schedule as ScheduleIcon, Cloud as CloudIcon, Business as BusinessIcon} from '@material-ui/icons'; @@ -29,6 +29,7 @@ const Admin = (props) => { const [firstRequest, setFirstRequest] = React.useState(true); const [orgRequest, setOrgRequest] = React.useState(true); const [modalUser, setModalUser] = React.useState({}); + const [orgName, setOrgName] = React.useState("") const [modalOpen, setModalOpen] = React.useState(false); const [cloudSyncModalOpen, setCloudSyncModalOpen] = React.useState(false); @@ -48,7 +49,10 @@ const Admin = (props) => { const [authentication, setAuthentication] = React.useState([]); const [schedules, setSchedules] = React.useState([]) const [files, setFiles] = React.useState([]) + const [selectedNamespace, setSelectedNamespace] = React.useState("default") + const [fileNamespaces, setFileNamespaces] = React.useState([]); const [selectedUser, setSelectedUser] = React.useState({}) + const [newUsername, setNewUsername] = React.useState(""); const [newPassword, setNewPassword] = React.useState(""); const [selectedUserModalOpen, setSelectedUserModalOpen] = React.useState(false) const [selectedAuthentication, setSelectedAuthentication] = React.useState({}) @@ -155,7 +159,7 @@ const Admin = (props) => { setTimeout(() => { getAppAuthentication() }, 1000) - alert.success("Successfully deleted authentication!") + //alert.success("Successfully deleted authentication!") } }), ) @@ -184,8 +188,10 @@ const Admin = (props) => { if (responseJson["success"] === false) { alert.error("Failed stopping schedule") } else { - getSchedules() - alert.success("Successfully stopped schedule!") + setTimeout(() => { + getSchedules() + }, 1500) + //alert.success("Successfully stopped schedule!") } }), ) @@ -365,6 +371,45 @@ const Admin = (props) => { }); } + const createSubOrg = (currentOrgId, name) => { + const data = { "name": name, "org_id": currentOrgId} + console.log(data) + const url = globalUrl + `/api/v1/orgs/${currentOrgId}/create_sub_org` + + fetch(url, { + mode: 'cors', + method: 'POST', + body: JSON.stringify(data), + credentials: 'include', + crossDomain: true, + withCredentials: true, + headers: { + 'Content-Type': 'application/json; charset=utf-8', + }, + }) + .then(response => + response.json().then(responseJson => { + if (responseJson["success"] === false) { + if (responseJson.reason !== undefined) { + alert.error(responseJson.reason) + } else { + alert.error("Failed creating suborg") + } + } else { + alert.success("Successfully created suborg!") + setSelectedUserModalOpen(false) + } + + setOrgName("") + setModalOpen(false) + }), + ) + .catch(error => { + alert.error("Err: " + error.toString()) + }); + + } + const onPasswordChange = () => { const data = { "username": selectedUser.username, "newpassword": newPassword } const url = globalUrl + '/api/v1/users/passwordchange'; @@ -457,6 +502,10 @@ const Admin = (props) => { if (responseJson["success"] === false) { alert.error("Failed getting org: ", responseJson.readon) } else { + if (responseJson.sync_features === undefined || responseJson.sync_features === null) { + responseJson.sync_features = {} + + } setSelectedOrganization(responseJson) var lists = { "active": { @@ -559,19 +608,19 @@ const Admin = (props) => { } // Horrible frontend fix for environments - const setDefaultEnvironment = (name) => { - // FIXME - add some check here ROFL - alert.info("Setting default env to " + name) + const setDefaultEnvironment = (environment) => { + // FIXME - add more checks to this + alert.info("Setting default env to " + environment.name) var newEnv = [] for (var key in environments) { - if (environments[key].Name == name) { + if (environments[key].id == environment.id) { if (environments[key].archived) { alert.error("Can't set archived to default") return } environments[key].default = true - } else if (environments[key].default == true && environments[key].name !== name) { + } else if (environments[key].default == true && environments[key].id !== environment.id) { environments[key].default = false } @@ -592,11 +641,15 @@ const Admin = (props) => { response.json().then(responseJson => { if (responseJson["success"] === false) { alert.error(responseJson.reason) - getEnvironments() + setTimeout(() => { + getEnvironments() + }, 1500) } else { setLoginInfo("") setModalOpen(false) - getEnvironments() + setTimeout(() => { + getEnvironments() + }, 1500) } }), ) @@ -632,23 +685,46 @@ const Admin = (props) => { }) } - const deleteEnvironment = (name) => { + const deleteEnvironment = (environment) => { // FIXME - add some check here ROFL - alert.info("Deleting environment " + name) + //const name = environment.name + + //alert.info("Modifying environment " + name) + //var newEnv = [] + //for (var key in environments) { + // if (environments[key].Name == name) { + // if (environments[key].default) { + // alert.error("Can't modify the default environment") + // return + // } + + // if (environments[key].type === "cloud" && !environments[key].archived) { + // alert.error("Can't modify cloud environments") + // return + // } + + // environments[key].archived = !environments[key].archived + // } + + // newEnv.push(environments[key]) + //} + const id = environment.id + + //alert.info("Modifying environment " + environment.Name) var newEnv = [] for (var key in environments) { - if (environments[key].Name == name) { + if (environments[key].id == id) { if (environments[key].default) { - alert.error("Can't delete the default environment") + alert.error("Can't modify the default environment") return } - if (environments[key].type === "cloud") { - alert.error("Can't delete the cloud environments") + if (environments[key].type === "cloud" && !environments[key].archived) { + alert.error("Can't modify cloud environments") return } - environments[key].archived = true + environments[key].archived = !environments[key].archived } newEnv.push(environments[key]) @@ -795,8 +871,16 @@ const Admin = (props) => { return response.json() }) .then((responseJson) => { - //console.log(responseJson) - setFiles(responseJson) + if (responseJson.files !== undefined && responseJson.files !== null) { + setFiles(responseJson.files) + } else { + setFiles([]) + } + + console.log("NAMESPACES: ", responseJson.namespaces) + if (responseJson.namespaces !== undefined && responseJson.namespaces !== null) { + setFileNamespaces(responseJson.namespaces) + } }) .catch(error => { alert.error(error.toString()) @@ -931,6 +1015,9 @@ const Admin = (props) => { } const getOrgs = () => { + // API no longer in use, as it's in handleInfo request + return + fetch(globalUrl + "/api/v1/orgs", { method: 'GET', headers: { @@ -1118,6 +1205,7 @@ const Admin = (props) => { alert.error("Failed setting user: " + responseJson.reason) } else { alert.success("Set the user field " + field + " to " + value) + setSelectedUserModalOpen(false) } }) .catch(error => { @@ -1257,6 +1345,44 @@ const Admin = (props) => { > Editing {selectedUser.username} + {isCloud ? + null + : +
+ { + setNewUsername(e.target.value) + }} + /> + +
+ } {isCloud ? null : @@ -1543,7 +1669,7 @@ const Admin = (props) => { {selectedOrganization.name.length > 0 ? - + :
@@ -1657,65 +1783,65 @@ const Admin = (props) => {
} Cloud sync features - - {Object.keys(selectedOrganization.sync_features).map(function(key, index) { - if (key === "schedule") { - return null - } + + {selectedOrganization.sync_features === undefined || selectedOrganization.sync_features === null ? null : Object.keys(selectedOrganization.sync_features).map(function(key, index) { + if (key === "schedule") { + return null + } - const item = selectedOrganization.sync_features[key] - const newkey = key.replaceAll("_", " ") - const griditem = { - "primary": newkey, - "secondary": item.description === undefined || item.description === null || item.description.length === 0 ? "Not defined yet" : item.description, - "limit": item.limit, - "usage": 0, - "data_collection": "None", - "active": item.active, - "icon": , - } + const item = selectedOrganization.sync_features[key] + const newkey = key.replaceAll("_", " ") + const griditem = { + "primary": newkey, + "secondary": item.description === undefined || item.description === null || item.description.length === 0 ? "Not defined yet" : item.description, + "limit": item.limit, + "usage": 0, + "data_collection": "None", + "active": item.active, + "icon": , + } - return ( - - - - ) - })} - - - {isCloud && selectedOrganization.subscriptions !== undefined && selectedOrganization.subscriptions !== null && selectedOrganization.subscriptions.length > 0 ? -
- - Your subscription{selectedOrganization.subscriptions.length > 1 ? "s" : ""} - - - {selectedOrganization.subscriptions.reverse().map((sub, index) => { - return ( - - - Type: {sub.level}
- Recurrence: {sub.recurrence}
- {sub.active ? -
- Started: {new Date(sub.startdate*1000).toISOString()}
- -
- : -
- Cancelled: {new Date(sub.cancellationdate*1000).toISOString()}
- - Status: Deactivated - -
- } - - - ) + return ( + + + + ) })} + + + {isCloud && selectedOrganization.subscriptions !== undefined && selectedOrganization.subscriptions !== null && selectedOrganization.subscriptions.length > 0 ? +
+ + Your subscription{selectedOrganization.subscriptions.length > 1 ? "s" : ""} + + + {selectedOrganization.subscriptions.reverse().map((sub, index) => { + return ( + + + Type: {sub.level}
+ Recurrence: {sub.recurrence}
+ {sub.active ? +
+ Started: {new Date(sub.startdate*1000).toISOString()}
+ +
+ : +
+ Cancelled: {new Date(sub.cancellationdate*1000).toISOString()}
+ + Status: Deactivated + +
+ } + + + ) + })}
@@ -1744,14 +1870,20 @@ const Admin = (props) => { }} > - {curTab === 1 ? "Add user" : "Add environment"} + {curTab === 1 ? "Add user" : curTab === 6 ? "Add Sub-Organization" : "Add environment"} {curTab === 1 && isCloud ? We'll send an email to invite them to your organization. - : null} + : + curTab === 6 ? + + The organization created will become a child of your current organization, and be available to you. + + : + null } {curTab === 1 ?
Username @@ -1801,6 +1933,31 @@ const Admin = (props) => { }
+ : curTab === 6 ? +
+ Name + { + setOrgName(event.target.value) + }} + /> +
: curTab === 5 ?
Environment Name @@ -1838,6 +1995,8 @@ const Admin = (props) => { } else { submitUser(modalUser) } + } else if (curTab === 6) { + createSubOrg(selectedOrganization.id, orgName) } else if (curTab === 5) { submitEnvironment(modalUser) } @@ -1889,7 +2048,11 @@ const Admin = (props) => { /> + { value={data.role} fullWidth onChange={(e) => { - console.log("VALUE: ", e.target.value) - - setUser(data.id, "role", e.target.value) - }} + console.log("VALUE: ", e.target.value) + setUser(data.id, "role", e.target.value) + }} style={{ backgroundColor: theme.palette.surfaceColor, color: "white", height: "50px" }} > @@ -1965,10 +2127,14 @@ const Admin = (props) => { } style ={{ minWidth: 135, maxWidth: 135, marginRight: 15,}} /> - + + { @@ -2021,7 +2187,9 @@ const Admin = (props) => { } } - getFiles() + setTimeout(() => { + getFiles() + }, 2500) } const uploadFile = (e) => { @@ -2061,6 +2229,30 @@ const Admin = (props) => { > + + {fileNamespaces !== undefined && fileNamespaces !== null && fileNamespaces.length > 1 ? + + Namespace + + + : null} + @@ -2095,7 +2287,15 @@ const Admin = (props) => { primary="File ID" /> - {files === undefined || files === null ? null : files.map((file, index) => { + {files === undefined || files === null || files.length === 0 ? null : files.map((file, index) => { + if (file.namespace === "") { + file.namespace = "default" + } + + if (file.namespace !== selectedNamespace) { + return null + } + var bgColor = "#27292d" if (index % 2 === 0) { bgColor = "#1f2023" @@ -2303,13 +2503,13 @@ const Admin = (props) => { style={{minWidth: 150, maxWidth: 150, overflow: "hidden"}} /> - {categories.map(data => { + {categories.map((data, index) => { if (data.apps.length === 0) { return null } return ( - + { bgColor = "#1f2023" } + return ( {

Environments

- Decides what Orborus environment to execute an action in a workflow in.Learn more + Decides what Orborus environment to execute an action in a workflow in. Learn more
+ } - + {/**/} {
: null - const organizationsTab = curTab === 7 ? + const organizationsTab = curTab === 6 ?

Organizations

@@ -2602,28 +2803,31 @@ const Admin = (props) => { style={{}} variant="contained" color="primary" - disabled onClick={() => { setModalOpen(true) }} > - Add organization + Add suborganization + { style={{minWidth: 150, maxWidth: 150}} /> - {organizations !== undefined && organizations !== null && organizations.length > 0 ? + {userdata.orgs !== undefined && userdata.orgs !== null && userdata.orgs.length > 0 ? - {organizations.map((data, index) => { + {userdata.orgs.map((data, index) => { const isSelected = props.userdata.active_org.id === undefined ? "False" : props.userdata.active_org.id === data.id ? "True" : "False" + const imagesize = 40 + const imageStyle = {width: imagesize, height: imagesize, pointerEvents: "none", } + const image = data.image === "" ? + {data.name} + : + {data.name} + + var bgColor = "#27292d" + if (index % 2 === 0) { + bgColor = "#1f2023" + } + return ( - + + {
: null - const hybridTab = curTab === 6 ? + const hybridTab = curTab === 7 ?

Hybrid

@@ -2719,7 +2939,7 @@ const Admin = (props) => { const iconStyle = {marginRight: 10} const data = -
+
{ Files /> Schedules /> {isCloud ? null : Environments/>} - {window.location.protocol == "http:" && window.location.port === "3000" ? Hybrid/> : null} - {window.location.protocol == "http:" && window.location.port === "3000" ? Organizations/> : null} - {window.location.protocol === "http:" && window.location.port === "3000" ? Categories/> : null} + {isCloud ? null : Organizations/>} + {/*window.location.protocol == "http:" && window.location.port === "3000" ? Hybrid/> : null*/} + {/*window.location.protocol === "http:" && window.location.port === "3000" ? Categories/> : null*/}
{organizationView} {authenticationView} - {appCategoryView} {usersView} {environmentView} {schedulesView} {filesView} {hybridTab} {organizationsTab} + {appCategoryView}
diff --git a/frontend/src/views/AngularWorkflow.jsx b/frontend/src/views/AngularWorkflow.jsx index 84c4a759..802ea112 100644 --- a/frontend/src/views/AngularWorkflow.jsx +++ b/frontend/src/views/AngularWorkflow.jsx @@ -2,12 +2,13 @@ import React, {useRef, useState, useEffect, useLayoutEffect} from 'react'; import { useInterval } from 'react-powerhooks'; import { useTheme } from '@material-ui/core/styles'; -import uuid from "uuid"; +import { v4 as uuidv4 } from 'uuid'; import {Link} from 'react-router-dom'; import { Prompt } from 'react-router' import { useBeforeunload } from 'react-beforeunload'; import ReactJson from 'react-json-view' import NestedMenuItem from "material-ui-nested-menu-item"; +import ReactMarkdown from 'react-markdown'; import {TextField, Drawer, Button, Paper, Grid, Tabs, InputAdornment, Tab, ButtonBase, Tooltip, Select, MenuItem, Divider, Dialog, Modal, DialogActions, DialogTitle, InputLabel, DialogContent, FormControl, IconButton, Menu, Input, FormGroup, FormControlLabel, Typography, Checkbox, Breadcrumbs, CircularProgress, Switch, Fade} from '@material-ui/core'; @@ -28,12 +29,14 @@ import { useAlert } from "react-alert"; import { validateJson, GetIconInfo } from "./Workflows.jsx"; import { GetParsedPaths } from "./Apps.jsx"; import ConfigureWorkflow from '../components/ConfigureWorkflow.jsx'; +import AuthenticationOauth2 from "../components/Oauth2Auth.jsx"; import ParsedAction from '../components/ParsedAction.jsx'; -import Scroll from 'react-scroll' -import { Element as ScrollElement, animateScroll as scroll, scrollSpy, scroller } from 'react-scroll' +//import Scroll from 'react-scroll' +//import { Element as ScrollElement, animateScroll as scroll, scrollSpy, scroller } from 'react-scroll' const surfaceColor = "#27292D" const inputColor = "#383B40" +const appSorter = "favorite_apps" // http://apps.cytoscape.org/apps/yfileslayoutalgorithms cytoscape.use(edgehandles); @@ -73,6 +76,10 @@ export function sortByKey(array, key) { }).reverse() } + if (array === undefined || array === null) { + return [] + } + return array.sort(function(a, b) { var x = a[key]; var y = b[key] return ((x < y) ? -1 : ((x > y) ? 1 : 0)) @@ -171,6 +178,7 @@ const AngularWorkflow = (props) => { const [authenticationModalOpen, setAuthenticationModalOpen] = React.useState(false); const [conditionsModalOpen, setConditionsModalOpen] = React.useState(false); const [newVariableName, setNewVariableName] = React.useState(""); + const [authenticationType, setAuthenticationType] = React.useState(""); const [newVariableDescription, setNewVariableDescription] = React.useState(""); const [newVariableValue, setNewVariableValue] = React.useState(""); const [workflowDone, setWorkflowDone] = React.useState(false) @@ -307,13 +315,16 @@ const AngularWorkflow = (props) => { } } - if (param.name === "startnode" && outersub.id !== undefined) { - console.log("SHOULD SET STARTNODE IN SUBFLOW SELECTION: ", outersub) - const innernode = outersub.actions.find(action => action.id === param.value) - console.log("FOUND NODE: ", innernode) - if (innernode !== undefined && subworkflowStartnode.id !== innernode.id) { - setSubworkflowStartnode(innernode) - } + console.log("PARAM: ", param) + //console.log("PARAMVAL: ", param.value) + if (param.name === "startnode" && param.value !== undefined && param.value !== null) { + //console.log("SHOULD SET STARTNODE IN SUBFLOW SELECTION: ", outersub) + //const innernode = outersub.actions.find(action => action.id === param.value) + //console.log("FOUND NODE: ", innernode) + //if (innernode !== undefined && subworkflowStartnode.id !== innernode.id) { + //setSubworkflowStartnode(JSON.parse(JSON.stringify(param.value))) + setSubworkflowStartnode(param.value) + //} /* const sub = responseJson.find(data => data.id === param.value) setSubworkflow(sub) @@ -330,6 +341,38 @@ const AngularWorkflow = (props) => { }); } + + function OuterLink(props) { + if (props.href.includes("http") || props.href.includes("mailto")) { + return {props.children} + } + return {props.children} + } + + function Img(props) { + return {props.alt} + } + + function CodeHandler(props) { + return ( +
+				
+					{props.value}
+				
+			
+ ) + } + + function Heading(props) { + const element = React.createElement(`h${props.level}`, {style: {marginTop: 40}}, props.children) + return ( + + {props.level !== 1 ? : null} + {element} + + ) + } + const generateApikey = () => { fetch(globalUrl+"/api/v1/generateapikey", { method: 'GET', @@ -403,7 +446,7 @@ const AngularWorkflow = (props) => { if (!responseJson.success) { alert.error("Failed to set app auth: "+responseJson.reason) } else { - getAppAuthentication(true) + getAppAuthentication(true, false) setAuthenticationModalOpen(false) // Needs a refresh with the new authentication.. @@ -741,9 +784,10 @@ const AngularWorkflow = (props) => { } } - const saveWorkflow = (curworkflow) => { + const saveWorkflow = (curworkflow, executionArgument, startNode) => { var success = false + if (isCloud && !isLoggedIn) { console.log("Should redirect to register with redirect.") window.location.href = `/register?view=/workflows/${props.match.params.key}&message=You need sign up to use workflows with Shuffle` @@ -875,6 +919,12 @@ const AngularWorkflow = (props) => { return response.json() }) .then((responseJson) => { + if (executionArgument !== undefined && startNode !== undefined) { + console.log("Running execution AFTER saving") + executeWorkflow(executionArgument, startNode, true) + return + } + if (!responseJson.success) { console.log(responseJson) alert.error("Failed to save: "+responseJson.reason) @@ -930,9 +980,22 @@ const AngularWorkflow = (props) => { } const monitorUpdates = () => { - const firstnode = cy.getElementById(workflow.start) + var firstnode = cy.getElementById(workflow.start) if (firstnode.length === 0) { - return false + var found = false + for (var key in workflow.actions) { + if (workflow.actions[key].isStartNode) { + console.log("Updating startnode") + workflow.start = workflow.actions[key].id + firstnode = cy.getElementById(workflow.actions[key].id) + found = true + break + } + } + + if (!found) { + return false + } } cy.elements().removeClass('success-highlight failure-highlight executing-highlight') @@ -941,17 +1004,21 @@ const AngularWorkflow = (props) => { return true } - const executeWorkflow = (executionArgument, startNode) => { - if (!lastSaved) { + const executeWorkflow = (executionArgument, startNode, hasSaved) => { + if (hasSaved === false) { //alert.error("You might have forgotten to save before executing.") + //const saveWorkflow = (curworkflow) => { + //setExecutionRunning(true) + setExecutionRequestStarted(true) + saveWorkflow(workflow, executionArgument, startNode) console.log("FIXME: Might have forgotten to save before executing.") + return } if (workflow.public) { alert.info("Save it to get a new version") } - var returncheck = monitorUpdates() if (!returncheck) { alert.error("No startnode set.") @@ -960,7 +1027,6 @@ const AngularWorkflow = (props) => { setVisited([]) setExecutionRequest({}) - setExecutionRequestStarted(true) stop() var curelements = cy.elements() @@ -1114,7 +1180,7 @@ const AngularWorkflow = (props) => { "http", ] - const getAppAuthentication = (reset) => { + const getAppAuthentication = (reset, updateAction) => { fetch(globalUrl+"/api/v1/apps/authentication", { method: 'GET', headers: { @@ -1140,6 +1206,8 @@ const AngularWorkflow = (props) => { newauth.push(responseJson.data[key]) } + + //{selectedAction.authentication.map(data => { if (cy !== undefined) { console.log("NEW AUTH = reset cy's onnodeselect") @@ -1152,6 +1220,85 @@ const AngularWorkflow = (props) => { setAppAuthentication(newauth) setAuthLoaded(true) + + if (updateAction === true) { + //console.log("Should update authentication for selectedAction!!") + //console.log(responseJson) + + if (selectedApp.authentication.required) { + //console.log("App requires auth!!") + // Setup auth here :) + var appUpdates = false + const authenticationOptions = [] + var findAuthId = "" + if (selectedAction.authentication_id !== null && selectedAction.authentication_id !== undefined && selectedAction.authentication_id.length > 0) { + findAuthId = selectedAction.authentication_id + } + + var tmpAuth = JSON.parse(JSON.stringify(responseJson.data)) + //console.log("FOUND AUTH: ", tmpAuth) + + //console.log("Checking authentication: ", tmpAuth) + var latest = 0 + for (var key in tmpAuth) { + var item = tmpAuth[key] + + const newfields = {} + for (var filterkey in item.fields) { + newfields[item.fields[filterkey].key] = item.fields[filterkey].value + } + + item.fields = newfields + if (item.app.name === selectedApp.name) { + authenticationOptions.push(item) + + // Always becoming the last one + if (item.edited > latest) { + latest = item.edited + selectedAction.selectedAuthentication = item + + for (var key in workflow.actions) { + console.log(workflow.actions[key].app_name) + if (workflow.actions[key].app_name == selectedApp.name) { + console.log("Setting auth at: ", workflow.actions[key], item.id) + workflow.actions[key].selectedAuthentication = item + workflow.actions[key].authentication_id = item.id + appUpdates = true + //if (workflow.actions[key].selectedAuthentication === undefined || workflow.actions[key].selectedAuthentication === null || workflow.actions[key].selectedAuthentication.length === 0) { + // console.log("Setting inner auth: ", workflow.actions[key]) + //} + } + } + } + } + } + + //if (item.id === findAuthId) { + // selectedAction.selectedAuthentication = item + //} + //console.log("ACTION: ", selectedAction) + + //console.log("OPTIONS: ", authenticationOptions) + selectedAction.authentication = authenticationOptions + //console.log("Authentication: ", authenticationOptions) + if (selectedAction.selectedAuthentication === null || selectedAction.selectedAuthentication === undefined || selectedAction.selectedAuthentication.length === "") { + selectedAction.selectedAuthentication = {} + } + + + if (appUpdates === true) { + setAuthenticationModalOpen(false) + setSelectedAction(selectedAction) + setWorkflow(workflow) + saveWorkflow(workflow) + alert.info("Added and updated authentication!") + } else { + alert.error("Failed to find new authentication - did it work?") + } + } else { + alert.info("No authentication to update") + } + } } else { setAuthLoaded(true) //alert.error("Failed getting authentications") @@ -1322,7 +1469,7 @@ const AngularWorkflow = (props) => { cy.add(edges) if (nodefound === true) { - const newId = uuid.v4() + const newId = uuidv4() cy.add({ group: "edges", data: { @@ -1522,7 +1669,7 @@ const AngularWorkflow = (props) => { var hiddenNodes = [] const onNodeDragStop = (event, selectedAction) => { const nodedata = event.target.data() - console.log("IN NODE DRAG STOP: ", nodedata) + //console.log("IN NODE DRAG STOP: ", nodedata) if (nodedata.id === selectedAction.id) { return } @@ -1765,6 +1912,7 @@ const AngularWorkflow = (props) => { // https://stackoverflow.com/questions/16677856/cy-onselect-callback-only-once const onNodeSelect = (event, newAppAuth) => { const data = event.target.data() + //console.log("NEW NODE ID: ", data.id) if (data.isButton) { //console.log("BUTTON CLICKED: ", data) if (data.buttonType === "delete") { @@ -1780,6 +1928,22 @@ const AngularWorkflow = (props) => { // cy.getElementById(currentNode.data.id).remove() // } //} + } else if (data.buttonType === "set_startnode" && data.type !== "TRIGGER") { + const parentNode = cy.getElementById(data.attachedTo) + if (parentNode !== null && parentNode !== undefined) { + var oldstartnode = cy.getElementById(workflow.start) + if (oldstartnode !== null && oldstartnode !== undefined && oldstartnode.length > 0) { + try { + oldstartnode[0].data("isStartNode", false) + } catch (e) { + console.log("Startnode error: ", e) + } + } + + workflow.start = parentNode.data('id') + parentNode.data('isStartNode', true) + } + } else if (data.buttonType === "copy") { console.log("COPY!") // 1. Find parent @@ -1789,7 +1953,7 @@ const AngularWorkflow = (props) => { if (parentNode !== null && parentNode !== undefined) { //parentNode.data() var newNodeData = JSON.parse(JSON.stringify(parentNode.data())) - newNodeData.id = uuid.v4() + newNodeData.id = uuidv4.v4() if (newNodeData.position !== undefined) { newNodeData.position = { "x": newNodeData.position.x+100, @@ -1801,6 +1965,7 @@ const AngularWorkflow = (props) => { newNodeData.errors = [] newNodeData.is_valid = true newNodeData.isValid = true + newNodeData.label = parentNode.data("label")+"_copy" cy.add({ group: 'nodes', @@ -1845,7 +2010,7 @@ const AngularWorkflow = (props) => { //for (var key in sourcebranches) { // var newbranch = JSON.parse(JSON.stringify(sourcebranches[key])) - // newbranch.id = uuid.v4() + // newbranch.id = uuidv4() // newbranch.source_id = newNodeData.id // cy.add({ // group: "edges", @@ -1855,7 +2020,7 @@ const AngularWorkflow = (props) => { for (var key in sourcebranches) { var newbranch = JSON.parse(JSON.stringify(sourcebranches[key])) - newbranch.id = uuid.v4() + newbranch.id = uuidv4() newbranch.source_id = newNodeData.id newbranch._id = newbranch.id @@ -1869,7 +2034,7 @@ const AngularWorkflow = (props) => { for (var key in destinationbranches) { var newbranch = JSON.parse(JSON.stringify(destinationbranches[key])) - newbranch.id = uuid.v4() + newbranch.id = uuidv4() newbranch.destination_id = newNodeData.id newbranch._id = newbranch.id @@ -1899,16 +2064,21 @@ const AngularWorkflow = (props) => { //const branch = workflow.branches.filter(branch => branch.source_id === data.id || branch.destination_id === data.id) //console.log("APPAUTH: ", newAppAuth) //console.log("BRANCHES: ", branch) + //console.log("CLICK: ", data) if (data.type === "ACTION") { + //setSelectedApp({}) + //setSelectedAction({}) var curaction = workflow.actions.find(a => a.id === data.id) + //console.log("PARAMS: ", curaction.parameters[0]) //console.log("INSIDE CURACTION: ", curaction) if (!curaction || curaction === undefined) { //event.target.unselect() - //alert.error("Action not found. Please remake it.") + alert.error("Action not found. Please remake it.") return } + const curapp = apps.find(a => a.name === curaction.app_name && ((a.app_version === curaction.app_version || (a.loop_versions !== null && a.loop_versions.includes(curaction.app_version))))) //console.log("APP: ", curapp) if (!curapp || curapp === undefined) { @@ -1922,16 +2092,32 @@ const AngularWorkflow = (props) => { actions: [curaction], } - console.log(tmpapp) - console.log(curaction) + //console.log(tmpapp) + //console.log(curaction) setSelectedApp(tmpapp) //setSelectedAction(JSON.parse(JSON.stringify(curaction))) setSelectedAction(curaction) //return } else { - //console.log("AUTHENTICATION: ", curapp.authentication) - setRequiresAuthentication(curapp.authentication.required && curapp.authentication.parameters !== undefined && curapp.authentication.parameters !== null) + //console.log(curapp.authentication) + setAuthenticationType(curapp.authentication.type === "oauth2" && curapp.authentication.redirect_uri !== undefined && curapp.authentication.redirect_uri !== null ? + { + "type": "oauth2", + "redirect_uri": curapp.authentication.redirect_uri, + "refresh_uri": curapp.authentication.refresh_uri, + "token_uri": curapp.authentication.token_uri, + "scope": curapp.authentication.scope, + "client_id": curapp.authentication.client_id, + "client_secret": curapp.authentication.client_secret, + } : { + "type": "" + } + ) + + const requiresAuth = curapp.authentication.required //&& ((curapp.authentication.parameters !== undefined && curapp.authentication.parameters !== null) || (curapp.authentication.type === "oauth2" && curapp.authentication.redirect_uri !== undefined && curapp.authentication.redirect_uri !== null)) + //console.log("AUTHCHECK: ", requiresAuth) + setRequiresAuthentication(requiresAuth) if (curapp.authentication.required) { //console.log("App requires auth.") // Setup auth here :) @@ -1976,6 +2162,16 @@ const AngularWorkflow = (props) => { //setSelectedAction(JSON.parse(JSON.stringify(curaction))) //console.log("CURAPP: ", curapp, selectedApp) + //console.log("ACTION: ", curaction) + if (curaction.parameters !== undefined && curaction.parameters !== null && curaction.parameters.length > 0) { + //console.log("params: ", curaction.parameters) + for (var key in curaction.parameters) { + if (curaction.parameters[key].options !== undefined && curaction.parameters[key].options !== null && curaction.parameters[key].options.length > 0 && curaction.parameters[key].value == "") { + curaction.parameters[key].value = curaction.parameters[key].options[0] + } + } + } + setSelectedApp(curapp) setSelectedAction(curaction) @@ -1998,14 +2194,19 @@ const AngularWorkflow = (props) => { //console.log("Should handle trigger "+data.triggertype) //console.log(data) const trigger_index = workflow.triggers.findIndex(a => a.id === data.id) - setSelectedTriggerIndex(trigger_index) - setSelectedTrigger(data) - setSelectedActionEnvironment(data.env) if (data.app_name === "Shuffle Workflow") { getAvailableWorkflows(trigger_index) getSettings() + } else if (data.app_name === "Webhook") { + if (workflow.triggers[trigger_index].parameters !== undefined) { + workflow.triggers[trigger_index].parameters[0] = {"name": "url", "value": referenceUrl+"webhook_"+selectedTrigger.id} + } } + + setSelectedTriggerIndex(trigger_index) + setSelectedTrigger(data) + setSelectedActionEnvironment(data.env) } else { alert.error("Can't handle "+data.type) } @@ -2237,7 +2438,7 @@ const AngularWorkflow = (props) => { continue } - parentlabel = item.label.toLowerCase().trim().replaceAll(" ", "_") + parentlabel = item.label === undefined ? "" : item.label.toLowerCase().trim().replaceAll(" ", "_") exampledata = GetExampleResult(item) for (var paramkey in dstdata.parameters) { const param = dstdata.parameters[paramkey] @@ -2257,6 +2458,7 @@ const AngularWorkflow = (props) => { if (dstdata.parameters[paramkey].value.length === 0) { dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}` } else { + dstdata.parameters[paramkey].value = `$${parentlabel}${foundresult}` //console.log("Skipping ", dstdata.parameters[paramkey], " because it already has a value") } } @@ -2434,7 +2636,7 @@ const AngularWorkflow = (props) => { const node = event.target const nodedata = event.target.data() if (nodedata.finished === false || (nodedata.id !== undefined && nodedata.is_valid === undefined)) { - console.log("RETURNING (NOT ADDING) NODE ADD FOR: ", nodedata) + //console.log("RETURNING (NOT ADDING) NODE ADD FOR: ", nodedata) return } @@ -2459,8 +2661,32 @@ const AngularWorkflow = (props) => { } if (nodedata.type === "ACTION") { + /* + const tmpView = localStorage.getItem(appSorter) + const nodekey = nodedata.id + if (tmpView === null || tmpView === undefined) { + const basedata = {} + basedata[nodekey] = 1 + localStorage.setItem(appSorter, JSON.stringify(basedata)) + } else { + try { + var parsed = JSON.parse(tmpView) + try { + parsed[nodekey] += 1 + } catch { + parsed[nodekey] = 1 + } + + localStorage.setItem(appSorter, JSON.stringify(parsed)) + } catch { + console.log("Bad data in tmpView: ", tmpView) + } + } + console.log("FAVORITeS: ", tmpView) + */ + if (workflow.actions.length === 1 && workflow.actions[0].id === workflow.start) { - const newEdgeUuid = uuid.v4() + const newEdgeUuid = uuidv4() const newcybranch = { "source": workflow.start, "target": nodedata.id, @@ -2477,7 +2703,7 @@ const AngularWorkflow = (props) => { console.log("SHOULD STITCH WITH STARTNODE") cy.add(edgeToBeAdded) } - + if (nodedata.app_name === "Shuffle Tools") { const iconInfo = GetIconInfo(nodedata) const svg_pin = `` @@ -2492,6 +2718,19 @@ const AngularWorkflow = (props) => { } } + if (nodedata.parameters !== undefined && nodedata.parameters !== null && !nodedata.label.endsWith("_copy")) { + var newparameters = [] + + for (var subkey in nodedata.parameters) { + var newparam = JSON.parse(JSON.stringify(nodedata.parameters[subkey])) + newparam.id = uuidv4() + newparam.value = "" + newparameters.push(newparam) + } + + nodedata.parameters = newparameters + } + if (workflow.actions === undefined || workflow.actions === null) { workflow.actions = [nodedata] } else { @@ -2500,13 +2739,19 @@ const AngularWorkflow = (props) => { setWorkflow(workflow) } else if (nodedata.type === "TRIGGER") { + if (nodedata.is_valid === false) { + alert.info("This trigger is not available to you") + node.remove() + return + } + if (workflow.triggers === undefined) { workflow.triggers = [nodedata] } else { workflow.triggers.push(nodedata) } - const newEdgeUuid = uuid.v4() + const newEdgeUuid = uuidv4() const newcybranch = { "source": nodedata.id, "target": workflow.start, @@ -2600,8 +2845,7 @@ const AngularWorkflow = (props) => { // } //} else { // //console.log("Shouldnt re-add info? ") - //} - + //} } const onEdgeRemoved = (event) => { @@ -2710,7 +2954,7 @@ const AngularWorkflow = (props) => { //console.log(event.keyCode) switch( event.keyCode ) { case 27: - console.log("ESCAPE") + //console.log("ESCAPE") if (configureWorkflowModalOpen === true) { setConfigureWorkflowModalOpen(false) } @@ -2741,22 +2985,22 @@ const AngularWorkflow = (props) => { case 67: if (previouskey === 17) { console.log("CTRL+C") - const filteredelements = cy.filter(function(element, i){ - return element.hasClass('selected') - }) + //const filteredelements = cy.filter(function(element, i){ + // return element.hasClass('selected') + //}) - for (var key in filteredelements) { - } + //for (var key in filteredelements) { + //} - var copyText = document.getElementById("copy_element_shuffle") - if (copyText !== undefined && copyText !== null) { - const clipboard = navigator.clipboard - if (clipboard === undefined) { - alert.error("Can only copy over HTTPS (port 3443)") - return - } - } - console.log("FILTERED: ", filteredelements) + //var copyText = document.getElementById("copy_element_shuffle") + //if (copyText !== undefined && copyText !== null) { + // const clipboard = navigator.clipboard + // if (clipboard === undefined) { + // alert.error("Can only copy over HTTPS (port 3443)") + // return + // } + //} + //console.log("FILTERED: ", filteredelements) } break; case 86: @@ -2770,10 +3014,10 @@ const AngularWorkflow = (props) => { } break; case 83: - if (previouskey === 17) { - event.preventDefault() - saveWorkflow() - } + //if (previouskey === 17) { + // event.preventDefault() + // saveWorkflow() + //} break; case 70: //if (previouskey === 17) { @@ -2915,7 +3159,7 @@ const AngularWorkflow = (props) => { setEstablished(true) // Validate if the node is just a node lol - console.log("CY: ", cy) + //console.log("CY: ", cy) //console.log("CY: ", cy.edgehandles()) //try { cy.edgehandles({ @@ -3064,6 +3308,50 @@ const AngularWorkflow = (props) => { const buttonColor = "rgba(255,255,255,0.9)" const buttonBackgroundColor = "#1f2023" + const addStartnodeButton = (event) => { + var parentNode = cy.$('#' + event.target.data("id")); + if (parentNode.data('isButton') || parentNode.data('buttonId')) + return + + if (parentNode.data("isStartNode")) { + return + } + + //parentNode.lock() + const px = parentNode.position('x') - 65 + const py = parentNode.position('y') - 45 + const circleId = newNodeId = uuidv4() + + parentNode.data('circleId', circleId) + + const iconInfo = { + "icon": "M9.4 2H15V12H8L7.6 10H2V17H0V0H9L9.4 2ZM9 10H11V8H13V6H11V4H9V6L8 4V2H6V4H4V2H2V4H4V6H2V8H4V6H6V8H8V6L9 8V10ZM6 6V4H8V6H6ZM9 6H11V8H9V6Z", + "iconColor": buttonColor, + "iconBackgroundColor": buttonBackgroundColor, + } + + const svg_pin = `` + const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin) + + cy.add({ + group: 'nodes', + data: { + weight: 30, + id: circleId, + name: "TEEEXT", + isButton: true, + buttonType: "set_startnode", + attachedTo: event.target.data("id"), + icon: svgpin_Url, + iconBackground: iconInfo.iconBackgroundColor, + is_valid: true, + }, + position: { x: px, y: py }, + locked: true + }) + //.unselectify() + } + const addCopyButton = (event) => { var parentNode = cy.$('#' + event.target.data("id")); if (parentNode.data('isButton') || parentNode.data('buttonId')) @@ -3071,8 +3359,8 @@ const AngularWorkflow = (props) => { //parentNode.lock() const px = parentNode.position('x') - 65 - const py = parentNode.position('y') - 25 - const circleId = newNodeId = uuid.v4() + const py = parentNode.position('y') - 5 + const circleId = newNodeId = uuidv4() parentNode.data('circleId', circleId) @@ -3111,8 +3399,8 @@ const AngularWorkflow = (props) => { //parentNode.lock() const px = parentNode.position('x') - 65 - const py = parentNode.position('y') + 25 - const circleId = newNodeId = uuid.v4() + const py = parentNode.position('y') + 35 + const circleId = newNodeId = uuidv4() parentNode.data('circleId', circleId) @@ -3172,6 +3460,7 @@ const AngularWorkflow = (props) => { if (!found) { addDeleteButton(event) addCopyButton(event) + addStartnodeButton(event) } } @@ -3465,7 +3754,7 @@ const AngularWorkflow = (props) => { } const stopSchedule = (trigger, triggerindex) => { - alert.info("Stopping schedule") + //alert.info("Stopping schedule") fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/schedule/"+trigger.id, { method: 'DELETE', headers: { @@ -3846,6 +4135,19 @@ const AngularWorkflow = (props) => { "description": "Simple HTTP webhook", "long_description": "Execute a workflow with an unauthicated POST request", }, + { + "name": "Schedule", + "type": "TRIGGER", + "status": "uninitialized", + "description": "Schedule execution time", + "trigger_type": "SCHEDULE", + "errors": null, + "large_image": "data:image/png;base64,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", + "label": "Schedule", + "is_valid": true, + "environment": "onprem", + "long_description": "Create a schedule based on cron", + }, { "name": "Shuffle Workflow", "type": "TRIGGER", @@ -3867,37 +4169,38 @@ const AngularWorkflow = (props) => { "description": "Wait for user input", "trigger_type": "USERINPUT", "errors": null, - "is_valid": true, + "is_valid": cloudSyncEnabled || isCloud ? true : false, "label": "User input", "environment": "cloud", "long_description": "Take user input to continue execution", }, { - "name": "Schedule", + "name": "Office365", "type": "TRIGGER", "status": "uninitialized", - "description": "Schedule execution time", - "trigger_type": "SCHEDULE", - "errors": null, - "large_image": "data:image/png;base64,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", - "label": "Schedule", - "is_valid": true, - "environment": "onprem", - "long_description": "Create a schedule based on cron", - }, - { - "name": "Email", - "type": "TRIGGER", - "status": "uninitialized", - "description": "Add your email provider", + "description": "Starts upon O365 email", "trigger_type": "EMAIL", "errors": null, "is_valid": cloudSyncEnabled || isCloud ? true : false, "label": "Email", "environment": "cloud", - "large_image": 'data:image/png;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/hAytodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Nzg4QTJBMjVEMDI1MTFFN0EwQUVDODc5QjYyQkFCMUQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6Nzg4QTJBMjZEMDI1MTFFN0EwQUVDODc5QjYyQkFCMUQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3ODhBMkEyM0QwMjUxMUU3QTBBRUM4NzlCNjJCQUIxRCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3ODhBMkEyNEQwMjUxMUU3QTBBRUM4NzlCNjJCQUIxRCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/bAEMAAwICAgICAwICAgMDAwMEBgQEBAQECAYGBQYJCAoKCQgJCQoM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', + "large_image": 'data:image/jpg;base64,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', "long_description": "Execute a workflow when you get an email", - }] + }, + { + "name": "Gmail", + "type": "TRIGGER", + "status": "uninitialized", + "description": "Trigger based on Gmail", + "trigger_type": "EMAIL", + "errors": null, + "is_valid": cloudSyncEnabled || isCloud ? true : false, + "label": "Email", + "environment": "cloud", + "large_image": 'data:image/jpg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/hAzFodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ1IDc5LjE2MzQ5OSwgMjAxOC8wOC8xMy0xNjo0MDoyMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QTIyMjgyMEYwMDJDMTFFQkJBOEE5OUJBM0MzMTA2RDIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QTIyMjgyMTAwMDJDMTFFQkJBOEE5OUJBM0MzMTA2RDIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozQTMwMDQxRTAwMEUxMUVCQkE4QTk5QkEzQzMxMDZEMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpBMjIyODIwRTAwMkMxMUVCQkE4QTk5QkEzQzMxMDZEMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/bAEMAAwICAwICAwMCAwMDAwMEBwUEBAQECQYHBQcKCQsL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', + "long_description": "Execute a workflow when you get an email", + } + ] const TriggersView = () => { const triggersViewStyle = { @@ -3980,14 +4283,14 @@ const AngularWorkflow = (props) => { return } - if (data.is_valid === false) { - alert.error(data.name+" requires hybrid version of Shuffle") - return - } + //if (data.is_valid === false) { + // alert.error(data.name+" requires hybrid version of Shuffle") + // return + //} const triggerLabel = getNextActionName(data.name) - newNodeId = uuid.v4() + newNodeId = uuidv4() const newposition = { "x": e.pageX-cycontainer.offsetLeft, "y": e.pageY-cycontainer.offsetTop, @@ -4154,7 +4457,7 @@ const AngularWorkflow = (props) => { return } - newNodeId = uuid.v4() + newNodeId = uuidv4() const actionType = "ACTION" const actionLabel = getNextActionName(app.name) var parameters = null @@ -4618,19 +4921,36 @@ const AngularWorkflow = (props) => { overflow: "auto", } + // Old static one + //var rightsidebarStyle = { + // position: "fixed", + // right: 0, + // top: appBarSize+1, + // height: "100%", + // bottom: 0, + // minWidth: 350, + // maxWidth: 350, + // borderLeft: "1px solid rgb(91, 96, 100)", + // overflow: "scroll", + // overflowX: "auto", + // overflowY: "auto", + // zIndex: 1000, + //} + var rightsidebarStyle = { position: "fixed", - right: 0, - top: appBarSize+1, - height: "100%", - bottom: 0, - minWidth: 350, - maxWidth: 350, - borderLeft: "1px solid rgb(91, 96, 100)", - overflow: "scroll", - overflowX: "auto", - overflowY: "auto", + top: appBarSize+25, + right: 25, + height: "80vh", + width: 350, + minWidth: 200, + maxWidth: 600, + maxHeight: "100vh", + border: "1px solid rgb(91, 96, 100)", zIndex: 1000, + borderRadius: theme.palette.borderRadius, + resize: "both", + overflow: "auto", } const setTriggerFolderWrapperMulti = event => { @@ -4773,6 +5093,7 @@ const AngularWorkflow = (props) => { if (actionlist.length === 0) { // FIXME: Have previous execution values in here actionlist.push({"type": "Execution Argument", "name": "Execution Argument", "value": "$exec", "highlight": "exec", "autocomplete": "exec", "example": "hello"}) + //actionlist.push({"type": "Key:Value store", "name": "Shuffle KV store", "value": "$shuffle_cache", "highlight": "shuffle_cache", "autocomplete": "shuffle_cache", "example": ""}) if (workflow.workflow_variables !== null && workflow.workflow_variables !== undefined && workflow.workflow_variables.length > 0) { for (var key in workflow.workflow_variables) { @@ -5191,7 +5512,7 @@ const AngularWorkflow = (props) => { const duplicateCondition = (conditionIndex) => { var newEdge = JSON.parse(JSON.stringify(selectedEdge.conditions[conditionIndex])) - const newUuid = uuid.v4() + const newUuid = uuidv4() newEdge.condition.id = newUuid newEdge.source.id = newUuid newEdge.destination.id = newUuid @@ -5303,7 +5624,7 @@ const AngularWorkflow = (props) => { } // FIXME - remove index - const conditionId = uuid.v4() + const conditionId = uuidv4() return(
@@ -5348,7 +5669,38 @@ const AngularWorkflow = (props) => { setSelectedTrigger(selectedTrigger) } - const setFolders = () => { + const setGmailFolders = () => { + console.log("In set gmail folders") + fetch(globalUrl+"/api/v1/triggers/gmail/getFolders?trigger_id="+selectedTrigger.id, { + method: "GET", + headers: {"content-type": "application/json"}, + credentials: "include", + }) + .then((response) => { + if (response.status !== 200) { + throw new Error("No folders :o!") + } + + return response.json() + }) + .then((responseJson) => { + if (responseJson !== undefined && responseJson !== null && responseJson.success !== false && responseJson.length > 0) { + setTriggerFolders(responseJson) + } + + if (workflow.triggers[selectedTriggerIndex].parameters.length === 0 && responseJson.length > 0) { + workflow.triggers[selectedTriggerIndex].parameters = [{"value": responseJson[0].displayName, "name": "outlookfolder", "id": responseJson[0].id}] + selectedTrigger.parameters = [{"value": responseJson[0].displayName, "name": "outlookfolder", "id": responseJson[0].id}] + setWorkflow(workflow) + setSelectedTrigger(selectedTrigger) + } + }) + .catch(error => { + console.log(error.toString()) + }) + } + + const setOutlookFolders = () => { fetch(globalUrl+"/api/v1/triggers/outlook/getFolders?trigger_id="+selectedTrigger.id, { method: "GET", headers: {"content-type": "application/json"}, @@ -5362,7 +5714,7 @@ const AngularWorkflow = (props) => { return response.json() }) .then((responseJson) => { - if (responseJson !== null && responseJson.success !== false) { + if (responseJson !== null && responseJson.success !== false && responseJson.length > 0) { setTriggerFolders(responseJson) } @@ -5403,22 +5755,33 @@ const AngularWorkflow = (props) => { // This is horrible hahah if (localFirstrequest) { getTriggerAuth() - setFolders() + setOutlookFolders() + setGmailFolders() setLocalFirstrequest(false) } - const outlookButton = + const handleButtonRequest = () => { + + } + + const gmailButton = selectedTrigger.name !== "Gmail" ? null : + + const outlookButton = selectedTrigger.name !== "Office365" ? null : + @@ -5471,17 +5910,18 @@ const AngularWorkflow = (props) => { if (Object.getOwnPropertyNames(triggerAuthentication).length > 0) { // Should get the folders if they don't already exist - if (triggerAuthentication.type === "outlook") { + if (triggerAuthentication.type === "outlook" || triggerAuthentication.type === "gmail" ) { triggerInfo =
{selectedTrigger.status === "running" ? null :
- Login + Change auth
{outlookButton} + {gmailButton} } @@ -5491,7 +5931,7 @@ const AngularWorkflow = (props) => {
- Select a folder + Select folders
{ setSubworkflowStartnode(e.target.value) //console.log("WF: ", workflow) - const branchId = uuid.v4() + const branchId = uuidv4() const newbranch = { "source_id": workflow.triggers[selectedTriggerIndex].id, "destination_id": e.target.value.id, @@ -6132,7 +6569,7 @@ const AngularWorkflow = (props) => { } } - if (workflow.id == subworkflow.id) { + if (workflow.id === subworkflow.id) { const cybranch = { group: "edges", source: newbranch.source_id, @@ -6144,6 +6581,7 @@ const AngularWorkflow = (props) => { cy.add(cybranch) } + console.log("Value to be set: ", e.target.value) try { workflow.triggers[selectedTriggerIndex].parameters[3].value = e.target.value.id } catch { @@ -6160,8 +6598,9 @@ const AngularWorkflow = (props) => { > {subworkflow.actions.map((action, index) => { //console.log(action) + const isParent = getParents(selectedTrigger).find(parent => parent.id === action.id) return ( - parent.id === action.id) ? "red" : "white"}} value={action}> + {action.label} ) @@ -6262,6 +6701,15 @@ const AngularWorkflow = (props) => { workflow.triggers[selectedTriggerIndex].parameters[1] = {"name": "tmp", "value": "webhook_"+selectedTrigger.id} workflow.triggers[selectedTriggerIndex].parameters[2] = {"name": "auth_headers", "value": ""} setWorkflow(workflow) + } else { + // Always update + const newUrl = referenceUrl+"webhook_"+selectedTrigger.id + console.log("Validating webhook url: ", newUrl) + if (newUrl !== workflow.triggers[selectedTriggerIndex].parameters[0].value) { + console.log("Url is wrong - updating") + workflow.triggers[selectedTriggerIndex].parameters[0].value = newUrl + setWorkflow(workflow) + } } const trigger_header_auth = workflow.triggers[selectedTriggerIndex].parameters.length > 2 ? workflow.triggers[selectedTriggerIndex].parameters[2].value : "" @@ -6376,6 +6824,12 @@ const AngularWorkflow = (props) => { console.log("Couldn't find webhook URI field: ", copyText) } }} + helperText={workflow.triggers[selectedTriggerIndex].parameters[0].value !== undefined && workflow.triggers[selectedTriggerIndex].parameters[0].value !== null && (workflow.triggers[selectedTriggerIndex].parameters[0].value.includes("localhost") || workflow.triggers[selectedTriggerIndex].parameters[0].value.includes("127.0.0.1"))? + + PS: This does NOT work with localhost. Use your local IP instead. + + : null + } InputProps={{ style:{ color: "white", @@ -6479,9 +6933,10 @@ const AngularWorkflow = (props) => { if (trigger.id === undefined) { return } - alert.info("Deleting mail trigger") - fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/outlook/"+trigger.id, { + alert.info("Stopping mail trigger") + const requesttype = triggerAuthentication.type + fetch(`${globalUrl}/api/v1/workflows/${props.match.params.key}/${requesttype}/${trigger.id}`, { method: 'DELETE', headers: { 'Content-Type': 'application/json', @@ -6526,21 +6981,23 @@ const AngularWorkflow = (props) => { const item = splitItem[key] const curfolder = triggerFolders.find(a => a.displayName === item) if (curfolder === undefined) { - alert.error("Something went wrong with outlook folder: "+item) + alert.error("Something went wrong with folder selection: "+item) return } folders.push(curfolder.id) } - alert.info("Creating outlook subscription with name " + trigger.name) const data = { "name": trigger.name, "folders": folders, "id": trigger.id, } - fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/outlook", { + const requesttype = triggerAuthentication.type + alert.info("Creating "+requesttype+" subscription with name " + trigger.name) + + fetch(globalUrl+"/api/v1/workflows/"+props.match.params.key+"/"+requesttype, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -6558,9 +7015,9 @@ const AngularWorkflow = (props) => { }) .then((responseJson) => { if (!responseJson.success) { - alert.error("Failed to start outlook: " + responseJson.reason) + alert.error("Failed to start trigger: " + responseJson.reason) } else { - alert.success("Successfully started outlook sub") + alert.success("Successfully started folder subscription trigger. Test it by sending yoursend an email") workflow.triggers[triggerindex].status = "running" trigger.status = "running" @@ -6599,7 +7056,6 @@ const AngularWorkflow = (props) => { } const param = trigger.parameters.find(param => param.name === "auth_headers") - console.log("PARAM: ", param) var auth = "" if (param !== undefined && param !== null) { auth = param.value @@ -6646,7 +7102,7 @@ const AngularWorkflow = (props) => { return } - alert.info("Stopping webhook") + //alert.info("Stopping webhook") fetch(globalUrl+"/api/v1/hooks/"+trigger.id+"/delete", { method: 'DELETE', @@ -6855,7 +7311,7 @@ const AngularWorkflow = (props) => { }} fullWidth color="primary" - placeholder={"+474823212,+460203042"} + placeholder={"+474823212132,+46020304242"} defaultValue={workflow.triggers[selectedTriggerIndex].parameters[4].value} onBlur={(event) => { workflow.triggers[selectedTriggerIndex].parameters[4].value = event.target.value @@ -6968,7 +7424,7 @@ const AngularWorkflow = (props) => {
- Interval (seconds) + Interval (UTC)
{ return null } - const FileMenu = () => { - const [newAnchor, setNewAnchor] = React.useState(null) - const [showShuffleMenu, setShowShuffleMenu] = React.useState(false) - - { /*const [showShuffleMenu, setShowShuffleMenu] = React.useState(true) */} - return ( -
- { - setShowShuffleMenu(false) - }} - > -
-

This menu is used to control the workflow itself.

- - Exit on Error
} - control={ - { - workflow.configuration.exit_on_error = !workflow.configuration.exit_on_error - setWorkflow(workflow) - setUpdate("exit_on_error_"+workflow.configuration.exit_on_error ? "true" : "false") - setShowShuffleMenu(false) - }} /> - } - /> - Start from top
} - control={ - { - workflow.configuration.start_from_top = !workflow.configuration.start_from_top - setWorkflow(workflow) - setUpdate("start_from_top_"+workflow.configuration.start_from_top ? "true" : "false") - setShowShuffleMenu(false) - }} /> - } - /> -
- - - - - - -
- ) - } - const WorkflowMenu = () => { const [newAnchor, setNewAnchor] = React.useState(null) const [showShuffleMenu, setShowShuffleMenu] = React.useState(false) @@ -7183,6 +7581,19 @@ const AngularWorkflow = (props) => {

This menu is used to control the workflow itself.

+ + Skip Notifications
} + control={ + { + workflow.configuration.skip_notifications = !workflow.configuration.skip_notifications + setWorkflow(workflow) + setUpdate("skip_notifications"+workflow.configuration.skip_notification? "true" : "false") + //setShowShuffleMenu(false) + }} /> + } + /> Exit on Error
} @@ -7191,7 +7602,7 @@ const AngularWorkflow = (props) => { workflow.configuration.exit_on_error = !workflow.configuration.exit_on_error setWorkflow(workflow) setUpdate("exit_on_error_"+workflow.configuration.exit_on_error ? "true" : "false") - setShowShuffleMenu(false) + //setShowShuffleMenu(false) }} /> } /> @@ -7203,7 +7614,7 @@ const AngularWorkflow = (props) => { workflow.configuration.start_from_top = !workflow.configuration.start_from_top setWorkflow(workflow) setUpdate("start_from_top_"+workflow.configuration.start_from_top ? "true" : "false") - setShowShuffleMenu(false) + //setShowShuffleMenu(false) }} /> } /> @@ -7275,7 +7686,7 @@ const AngularWorkflow = (props) => { @@ -7368,7 +7779,6 @@ const AngularWorkflow = (props) => { - {/* */} {workflow.configuration !== null && workflow.configuration !== undefined && workflow.configuration.exit_on_error !== undefined ? : null}
@@ -7391,6 +7801,9 @@ const AngularWorkflow = (props) => {
{ copy = copy.src } - console.log("NEW: ", copy) - console.log("NAVIGATOR: ", navigator) - const clipboard = navigator.clipboard if (clipboard === undefined) { alert.error("Can only copy over HTTPS (port 3443)") return } - navigator.clipboard.writeText(JSON.stringify(copy)) + var stringified = JSON.stringify(copy) + if (stringified.startsWith("\"") && stringified.endsWith("\"")) { + stringified = stringified.substring(1, stringified.length-1) + } + + //console.log("NEW: ", stringified) + + navigator.clipboard.writeText(stringified) copyText.select() copyText.setSelectionRange(0, 99999) /* For mobile devices */ @@ -7680,7 +8097,7 @@ const AngularWorkflow = (props) => { } const executionModal = - setExecutionModalOpen(false)} style={{resize: "both", overflow: "auto",}} PaperProps={{style: {resize: "both", overflow: "auto", minWidth: 400, maxWidth: 400, backgroundColor: "#1F2023", color: "white", fontSize: 18}}}> + setExecutionModalOpen(false)} style={{resize: "both", overflow: "auto", zIndex: 10005}} PaperProps={{style: {resize: "both", overflow: "auto", minWidth: 400, maxWidth: 400, backgroundColor: "#1F2023", color: "white", fontSize: 18, zIndex: 10005}}}> {executionModalView === 0 ?
@@ -7717,7 +8134,7 @@ const AngularWorkflow = (props) => { } return ( - + {}} onMouseOut={() => {}} onClick={() => { if ((data.result === undefined || data.result === null || data.result.length === 0) && data.status !== "FINISHED" && data.status !== "ABORTED") { @@ -7792,12 +8209,12 @@ const AngularWorkflow = (props) => {
-

Executing Workflow

- +

Execution info

+
} +
+ {selectedResult.action.parameters !== null && selectedResult.action.parameters !== undefined ? +
+ + + Variables + + {selectedResult.action.parameters.map((data, index) => { + if (data.value.length === 0) { + return null + } + + if (data.example !== undefined && data.example !== null && data.example.includes("***")) { + return null + } + + return ( +
+ + {data.name}: {data.value} + +
+ ) + })} +
+ : + null + } +
@@ -8267,6 +8734,7 @@ const AngularWorkflow = (props) => { elements={elements} minZoom={0.35} maxZoom={2.00} + wheelSensitivity={0.25} style={{width: bodyWidth-leftBarSize-15, height: bodyHeight-appBarSize-5, backgroundColor: surfaceColor}} stylesheet={cystyle} boxSelectionEnabled={true} @@ -8388,7 +8856,7 @@ const AngularWorkflow = (props) => { "name": newVariableName, "description": "An execution variable", "value": "", - "id": uuid.v4(), + "id": uuidv4(), }) } @@ -8489,16 +8957,20 @@ const AngularWorkflow = (props) => { - + + Submit + : null + const AuthenticationData = (props) => { const selectedApp = props.app @@ -8549,16 +9022,18 @@ const AngularWorkflow = (props) => { usage: [{ workflow_id: workflow.id, }], - id: uuid.v4(), + id: uuidv4(), active: true, }) - if (selectedApp.authentication === undefined) { - return null - } - - if (selectedApp.authentication.parameters === null || selectedApp.authentication.parameters === undefined || selectedApp.authentication.parameters.length === 0) { - return null + if (selectedApp.authentication === undefined || selectedApp.authentication.parameters === null || selectedApp.authentication.parameters === undefined || selectedApp.authentication.parameters.length === 0) { + return ( + + + {selectedApp.name} does not require authentication + + + ) } authenticationOption.app.actions = [] @@ -8584,8 +9059,12 @@ const AngularWorkflow = (props) => { if (selectedApp.authentication.parameters[key].value !== undefined && selectedApp.authentication.parameters[key].value !== null && selectedApp.authentication.parameters[key].value.length > 0) { authenticationOption.fields[selectedApp.authentication.parameters[key].name] = selectedApp.authentication.parameters[key].value } else { - alert.info("Field "+selectedApp.authentication.parameters[key].name+" can't be empty") - return + if (selectedApp.authentication.parameters[key].schema.type === "bool") { + authenticationOption.fields[selectedApp.authentication.parameters[key].name] = "false" + } else { + alert.info("Field "+selectedApp.authentication.parameters[key].name+" can't be empty") + return + } } } } @@ -8635,10 +9114,12 @@ const AngularWorkflow = (props) => { authenticationOption.label = selectedApp.name+" authentication" } + //console.log( return (
+
Authentication for {selectedApp.name}
- What is this?
+ What is app authentication?
These are required fields for authenticating with {selectedApp.name}
Name - what is this used for? @@ -8682,6 +9163,7 @@ const AngularWorkflow = (props) => { defaultValue={"false"} fullWidth onChange={(e) => { + console.log("Value: ", e.target.value) authenticationOption.fields[data.name] = e.target.value }} style={{backgroundColor: theme.palette.surfaceColor, color: "white", height: 50}} @@ -8791,12 +9273,13 @@ const AngularWorkflow = (props) => { }}> - + : null // This whole part is redundant. Made it part of Arguments instead. + //console.log(selectedApp) const authenticationModal = authenticationModalOpen ? { style: { backgroundColor: surfaceColor, color: "white", - minWidth: 600, + minWidth: 1100, + minHeight: 700, + maxHeight: 700, padding: 15, + overflow: "hidden", }, }} > - { +
+ {selectedApp.reference_info === undefined || selectedApp.reference_info === null || selectedApp.reference_info.github_url === undefined || selectedApp.reference_info.github_url === null || selectedApp.reference_info.github_url.length === 0 ? + + {`Documentation + + : + + {`Documentation + + } +
+ { setAuthenticationModalOpen(false) if (configureWorkflowModalOpen) { setSelectedAction({}) @@ -8824,8 +9321,60 @@ const AngularWorkflow = (props) => { }}> -
Authentication for {selectedApp.name}
- +
+
+ {authenticationType.type === "oauth2" ? + + : + + } +
+
+ {selectedApp.documentation === undefined || selectedApp.documentation === null || selectedApp.documentation.length === 0 ? + + + {selectedApp.description} + + + + There is currently no extended documentation available for this app. + + + Want help with this app? Join the Discord! + + + + Want to help change this app? + + {selectedApp.reference_info === undefined || selectedApp.reference_info === null || selectedApp.reference_info.github_url === undefined || selectedApp.reference_info.github_url === null || selectedApp.reference_info.github_url.length === 0 ? + + + Check it out on Github! + + + : + + + Check it out on Github! + + + } + + : + + } +
+
: null //const loadedCheck = isLoaded && isLoggedIn && workflowDone ? @@ -8858,10 +9407,10 @@ const AngularWorkflow = (props) => { //console.log("SCROLL IS NOT 0: ", scrollConfig.top) //rightSideActionView.scrollTop = scrollConfig.top setTimeout(() => { - scroller.scrollTo('elements_wrapper', { - containerId: 'rightside_actions', - offset: scrollConfig.top, - }) + //scroller.scrollTo('elements_wrapper', { + // containerId: 'rightside_actions', + // offset: scrollConfig.top, + //}) if (scrollConfig.selected !== undefined && scrollConfig.selected !== null) { const selectedField = document.getElementById(scrollConfig.selected) @@ -8889,15 +9438,15 @@ const AngularWorkflow = (props) => { return (
- {loadedCheck} -
) + // + // } export default AngularWorkflow diff --git a/frontend/src/views/AppCreator.jsx b/frontend/src/views/AppCreator.jsx index 24006b3a..204bfb8a 100644 --- a/frontend/src/views/AppCreator.jsx +++ b/frontend/src/views/AppCreator.jsx @@ -6,7 +6,7 @@ import {BrowserView, MobileView} from "react-device-detect"; import {Paper, Typography, FormControlLabel, Button, Divider, Select, MenuItem, FormControl, Switch, Dialog, DialogTitle, DialogContent, DialogActions, TextField, Tooltip, Breadcrumbs, CircularProgress, Chip} from '@material-ui/core'; import {LockOpen as LockOpenIcon, FileCopy as FileCopyIcon, Delete as DeleteIcon, Remove as RemoveIcon, Add as AddIcon, CheckCircle as CheckCircleIcon, AttachFile as AttachFileIcon, Apps as AppsIcon, ErrorOutline as ErrorOutlineIcon} from '@material-ui/icons'; -import uuid from "uuid"; +import { v4 as uuidv4 } from 'uuid'; import {Link} from 'react-router-dom'; import YAML from 'yaml' import ChipInput from 'material-ui-chip-input' @@ -66,6 +66,7 @@ const appIconStyle = { marginLeft: "5px", } + const useStyles = makeStyles({ notchedOutline: { borderColor: "#f85a3e !important" @@ -200,8 +201,7 @@ const AppCreator = (props) => { const increaseAmount = 50 const actionNonBodyRequest = ["GET", "HEAD", "DELETE", "CONNECT"] const actionBodyRequest = ["POST", "PUT", "PATCH",] - //const authenticationOptions = ["No authentication", "API key", "Bearer auth", "Basic auth", "JWT", "Oauth2"] - const authenticationOptions = ["No authentication", "API key", "Bearer auth", "Basic auth"] + const authenticationOptions = ["No authentication", "API key", "Bearer auth", "Basic auth", "Oauth2", "JWT"] const apikeySelection = ["Header", "Query",] const [name, setName] = useState(""); @@ -220,6 +220,11 @@ const AppCreator = (props) => { const [newWorkflowCategories, setNewWorkflowCategories] = React.useState([]); const [parameterName, setParameterName] = useState(""); const [parameterLocation, setParameterLocation] = useState(apikeySelection.length > 0 ? apikeySelection[0] : ""); + const [refreshUrl, setRefreshUrl] = useState(""); + const [oauth2Scopes, setOauth2Scopes] = useState([]); + const [projectCategories, setProjectCategories] = useState([]); + const [selectedCategory, setSelectedCategory] = useState(""); + const [urlPath, setUrlPath] = useState(""); //const [urlPathQueries, setUrlPathQueries] = useState([{"name": "test", "required": false}]); const [urlPathQueries, setUrlPathQueries] = useState([]); @@ -228,6 +233,7 @@ const AppCreator = (props) => { const [firstrequest, setFirstrequest] = React.useState(true) const [basedata, setBasedata] = React.useState({}) const [actions, setActions] = useState([]) + const [filteredActions, setFilteredActions] = useState([]) const [errorCode, setErrorCode] = useState("") const [appBuilding, setAppBuilding] = useState(false) const [extraBodyFields, setExtraBodyFields] = useState([]) @@ -285,6 +291,7 @@ const AppCreator = (props) => { "method": actionNonBodyRequest[0], }); + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" useEffect(() => { @@ -388,7 +395,7 @@ const AppCreator = (props) => { const handleGetRef = (parameter, data) => { try { if (parameter === null || parameter["$ref"] === undefined) { - console.log("$ref not found in getref: ") + //console.log("$ref not found in getref for: ", parameter) return parameter } } catch (e) { @@ -417,12 +424,6 @@ const AppCreator = (props) => { } return newitem - - //console.log("Should get ", parameter["$ref"]) - //const subkeys = parameter["$ref"].split("/") - // setBasedata(data) - - // handleGetReference(parameter["$ref"]) } // Sets the data up as it should be at later points @@ -468,7 +469,18 @@ const AppCreator = (props) => { document.title = "Apps - "+data.info.title if (data.info["x-logo"] !== undefined) { - setFileBase64(data.info["x-logo"]) + + if (data.info["x-logo"].url !== undefined) { + console.log("PARSED LOGO: ", data.info["x-logo"].url) + setFileBase64(data.info["x-logo"].url) + } else { + setFileBase64(data.info["x-logo"]) + } + console.log("") + console.log("") + console.log("LOGO: ", data.info["x-logo"]) + console.log("") + console.log("") } if (data.info.contact !== undefined) { @@ -524,6 +536,7 @@ const AppCreator = (props) => { var newActions = [] var wordlist = {} + var all_categories = [] if (data.paths !== null && data.paths !== undefined) { for (let [path, pathvalue] of Object.entries(data.paths)) { for (let [method, methodvalue] of Object.entries(pathvalue)) { @@ -533,8 +546,11 @@ const AppCreator = (props) => { } if (!allowedfunctions.includes(method.toUpperCase())) { - console.log("Invalid method: ", method, "data: ", methodvalue) - alert.info("Skipped method (not allowed): "+method) + // Typical YAML issue + if (method !== "parameters") { + console.log("Invalid method: ", method, "data: ", methodvalue) + alert.info("Skipped method (not allowed): "+method) + } continue } @@ -543,6 +559,8 @@ const AppCreator = (props) => { tmpname = methodvalue.operationId } + tmpname = tmpname.replaceAll(".", " ") + var newaction = { "name": tmpname, "description": methodvalue.description, @@ -557,8 +575,24 @@ const AppCreator = (props) => { "example_response": "", } - //console.log("Schema is application/json: ", methodvalue) - //console.log("DATA", data) + // Finding category + if (path.includes("/")) { + const pathsplit = path.split("/") + var categoryindex = -1 + // Stupid way of finding a category/grouping + for (var key in pathsplit) { + if (pathsplit[key].length > 0 && pathsplit[key] !== "v1" && pathsplit[key] !== "v2" && pathsplit[key] !== "api" && pathsplit[key] !== "1.0" && pathsplit[key] !== "apis") { + newaction["category"] = pathsplit[key] + if (!all_categories.includes(pathsplit[key])) { + all_categories.push(pathsplit[key]) + } + break + } + } + } + + + // Typescript? I think not ;) if (methodvalue["requestBody"] !== undefined) { //console.log("Handle requestbody: ", methodvalue["requestBody"]) if (methodvalue["requestBody"]["content"] !== undefined) { @@ -610,15 +644,13 @@ const AppCreator = (props) => { } } } else { + //console.log("REQUESTBODY: ", methodvalue["requestBody"]["content"]) if (methodvalue["requestBody"]["content"]["example"] !== undefined) { if (methodvalue["requestBody"]["content"]["example"]["example"] !== undefined) { newaction["body"] = methodvalue["requestBody"]["content"]["example"]["example"] //JSON.stringify(tmpobject, null, 2) } - } - - //console.log(methodvalue["requestBody"]["content"]) - if (methodvalue["requestBody"]["content"]["multipart/form-data"] !== undefined) { + } else if (methodvalue["requestBody"]["content"]["multipart/form-data"] !== undefined) { if (methodvalue["requestBody"]["content"]["multipart/form-data"]["schema"] !== undefined && methodvalue["requestBody"]["content"]["multipart/form-data"]["schema"] !== null) { if (methodvalue["requestBody"]["content"]["multipart/form-data"]["schema"]["type"] === "object") { const fieldname = methodvalue["requestBody"]["content"]["multipart/form-data"]["schema"]["properties"]["fieldname"] @@ -628,6 +660,63 @@ const AppCreator = (props) => { } } } + } else { + + var schemas = [] + const content = methodvalue["requestBody"]["content"] + if (content !== undefined && content !== null) { + //console.log("CONTENT: ", content) + for (const [subkey, subvalue] of Object.entries(content)) { + if (subvalue["schema"] !== undefined) { + //console.log("SCHEMA: ", subvalue["schema"]) + if (subvalue["schema"]["$ref"] !== undefined) { + //console.log("SCHEMA FOUND REF!") + + if (!schemas.includes(subvalue["schema"]["$ref"])) { + schemas.push(subvalue["schema"]["$ref"]) + } + } + } else { + console.log("ERROR: couldn't find schema for ", subvalue, method) + } + } + } + + if (schemas.length === 1) { + const parameter = handleGetRef({"$ref": schemas[0]}, data) + + if (parameter.properties !== undefined && parameter["type"] === "object") { + var newbody = {} + for (var propkey in parameter.properties) { + const parsedkey = propkey.replaceAll(" ", "_").toLowerCase() + if (parameter.properties[propkey].type === undefined) { + console.log("Skipping (4): ", parameter.properties[propkey]) + continue + } + + if (parameter.properties[propkey].type === "string") { + if (parameter.properties[propkey].description !== undefined) { + newbody[parsedkey] = parameter.properties[propkey].description + } else { + newbody[parsedkey] = "" + } + } else if (parameter.properties[propkey].type.includes("int") || parameter.properties[propkey].type.includes("uint64")) { + newbody[parsedkey] = 0 + } else if (parameter.properties[propkey].type.includes("boolean")) { + newbody[parsedkey] = false + } else if (parameter.properties[propkey].type.includes("array")) { + newbody[parsedkey] = [] + } else { + console.log("CANT HANDLE JSON TYPE (4)", parameter.properties[propkey].type, parameter.properties[propkey]) + newbody[parsedkey] = [] + } + } + + newaction["body"] = JSON.stringify(newbody, null, 2) + } else { + console.log("CANT HANDLE PARAM: (4) ", parameter.properties) + } + } } } } @@ -669,7 +758,7 @@ const AppCreator = (props) => { for (var propkey in parameter.properties) { const parsedkey = propkey.replaceAll(" ", "_").toLowerCase() if (parameter.properties[propkey].type === undefined) { - console.log("Skipping: ", parameter.properties[propkey]) + console.log("Skipping (1): ", parameter.properties[propkey]) continue } @@ -681,6 +770,13 @@ const AppCreator = (props) => { } } else if (parameter.properties[propkey].type.includes("int")) { newbody[parsedkey] = 0 + } else if (parameter.properties[propkey].type.includes("boolean")) { + newbody[parsedkey] = false + } else if (parameter.properties[propkey].type.includes("array")) { + //console.log("Added empty array. Base is: ", parameter.properties[propkey].type) + + //const parameter = handleGetRef(selectedExample["content"]["application/json"]["schema"], data) + newbody[parsedkey] = [] } else { console.log("CANT HANDLE JSON TYPE ", parameter.properties[propkey].type, parameter.properties[propkey]) newbody[parsedkey] = [] @@ -705,7 +801,7 @@ const AppCreator = (props) => { for (var propkey in parameter.properties) { const parsedkey = propkey.replaceAll(" ", "_").toLowerCase() if (parameter.properties[propkey].type === undefined) { - console.log("Skipping: ", parameter.properties[propkey]) + console.log("Skipping (2): ", parameter.properties[propkey]) continue } @@ -717,6 +813,8 @@ const AppCreator = (props) => { } } else if (parameter.properties[propkey].type.includes("int")) { newbody[parsedkey] = 0 + } else if (parameter.properties[propkey].type.includes("boolean")) { + newbody[parsedkey] = false } else { console.log("CANT HANDLE JSON TYPE (2) ", parameter.properties[propkey].type) newbody[parsedkey] = [] @@ -740,7 +838,7 @@ const AppCreator = (props) => { for (var propkey in parameter.properties) { const parsedkey = propkey.replaceAll(" ", "_").toLowerCase() if (parameter.properties[propkey].type === undefined) { - console.log("Skipping: ", parameter.properties[propkey]) + console.log("Skipping (3): ", parameter.properties[propkey]) continue } @@ -763,7 +861,7 @@ const AppCreator = (props) => { //newaction.example_response = JSON.stringify(parameter.properties, null, 2) } else { //newaction.example_response = parameter.properties - console.log("CANT HANDLE PARAM: (2) ", parameter.properties) + console.log("CANT HANDLE PARAM: (3) ", parameter.properties) } } } @@ -867,6 +965,7 @@ const AppCreator = (props) => { newaction.errors.push("Missing name") } } + newActions.push(newaction) } } @@ -897,18 +996,80 @@ const AppCreator = (props) => { } - console.log("SECURITYSCHEMES: ", securitySchemes) + //console.log("SECURITYSCHEMES: ", securitySchemes) if (securitySchemes !== undefined) { // FIXME: Should add Oauth2 (Microsoft) and JWT (Wazuh) //console.log("SECURITY: ", securitySchemes) //if (Object.entries(securitySchemes) > 1 && var newauth = [] for (const [key, value] of Object.entries(securitySchemes)) { - if (value.scheme === "bearer") { + console.log(key, value) + if (key === "jwt") { + setAuthenticationOption("JWT") + setAuthenticationRequired(true) + + if (value.in !== undefined && value.in !== null && value.in.length > 0) { + setParameterName(value.in) + } + } else if (value.scheme === "bearer") { setAuthenticationOption("Bearer auth") setAuthenticationRequired(true) - } else if (key === "oauth2") { - alert.info("Can't handle Oauth2 auth yet.") + } else if (key === "Oauth2" || key === "Oauth2c") { + //alert.info("Can't handle Oauth2 auth yet.") + setAuthenticationOption("Oauth2") + setAuthenticationRequired(true) + + //console.log("FLOW-1: ", value) + const flowkey = value.flow === undefined ? "flows" : "flow" + //console.log("FLOW: ", value[flowkey]) + const basekey = value[flowkey].authorizationCode !== undefined ? "authorizationCode" : "implicit" + //console.log("FLOW2: ", value[flowkey][basekey]) + if (value[flowkey] !== undefined && value[flowkey][basekey] !== undefined) { + if (value[flowkey][basekey].authorizationUrl !== undefined && parameterName.length === 0) { + setParameterName(value[flowkey][basekey].authorizationUrl) + } + + var tokenUrl = "" + if (value[flowkey][basekey].tokenUrl !== undefined) { + setParameterLocation(value[flowkey][basekey].tokenUrl) + tokenUrl = value[flowkey][basekey].tokenUrl + } else { + setParameterLocation("") + } + + if (value[flowkey][basekey].refreshUrl !== undefined) { + setRefreshUrl(value[flowkey][basekey].refreshUrl) + } else if (tokenUrl.length > 0) { + setRefreshUrl(tokenUrl) + } + + if (value[flowkey][basekey].scopes !== undefined && value[flowkey][basekey].scopes !== null) { + if (value[flowkey][basekey].scopes.length > 0) { + setOauth2Scopes(value[flowkey][basekey].scopes) + } else { + var newscopes = [] + for (let [scopekey, scopevalue] of Object.entries(value[flowkey][basekey].scopes)) { + if (scopekey.startsWith("http")) { + const scopekeysplit = scopekey.split("/") + if (scopekeysplit.length < 5) { + console.log("Skipping scope: ", scopekey) + alert.info("Skipping scope: "+scopekey) + continue + } + + //console.log("Checking scope for: ", scopekey, scopekeysplit.length) + } + + newscopes.push(scopekey) + } + + setOauth2Scopes(newscopes) + } + } + } else { + console.log("Bad flowkey and basekey for oauth2: ", flowkey, basekey) + } + } else if (key === "ApiKeyAuth") { setAuthenticationOption("API key") @@ -929,11 +1090,12 @@ const AppCreator = (props) => { setAuthenticationOption("Oauth2") setAuthenticationRequired(true) } else { - newauth.push({ - "name": key, - "type": value.in, - "example": "", - }) + alert.error("Couldn't handle AUTH type: ", key) + //newauth.push({ + // "name": key, + // "type": value.in, + // "example": "", + //}) } } @@ -948,12 +1110,15 @@ const AppCreator = (props) => { setActionAmount(newActions.length) } - if (newActions.length > 1000) { + //const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" + if (newActions.length > 1000 && isCloud) { alert.error("Cut down actions from "+newActions.length+" to 999 because of limit") newActions = newActions.slice(0,999) } + setProjectCategories(all_categories) setActions(newActions) + setFilteredActions(newActions) setIsAppLoaded(true) } @@ -970,7 +1135,7 @@ const AppCreator = (props) => { const basePath = "/"+(splitBase.slice(3, )).join("/") const data = { - "openapi": "3.0", + "openapi": "3.0.0", "info": { "title": name, "description": description, @@ -1097,9 +1262,47 @@ const AppCreator = (props) => { } if (item.queries.length > 0) { + var skipped = false for (var querykey in item.queries) { const queryitem = item.queries[querykey] + if (queryitem.name.toLowerCase() == "url") { + console.log(item.name+" uses a bad query: url") + continue + //skipped = true + //break + } + + if (queryitem.name.toLowerCase() == "url" + || queryitem.name.toLowerCase() == "body" + || queryitem.name.toLowerCase() == "self" + || queryitem.name.toLowerCase() == "ssl_verify" + || queryitem.name.toLowerCase() == "queries" + || queryitem.name.toLowerCase() == "headers" + || queryitem.name.includes("[") + || queryitem.name.includes("]") + || queryitem.name.includes("{") + || queryitem.name.includes("}") + || queryitem.name.includes("(") + || queryitem.name.includes(")") + || queryitem.name.includes("!") + || queryitem.name.includes("@") + || queryitem.name.includes("#") + || queryitem.name.includes("$") + || queryitem.name.includes("%") + || queryitem.name.includes("^") + || queryitem.name.includes("&") + || queryitem.name.includes(":") + || queryitem.name.includes(";") + || queryitem.name.includes("<") + || queryitem.name.includes(">") + || queryitem.name.includes("\"") + || queryitem.name.includes("\'") + ) { + console.log(item.name+" error: uses a bad query - not adding: ", queryitem.name) + continue + } + var newitem = { "in": "query", "name": queryitem.name, @@ -1117,12 +1320,27 @@ const AppCreator = (props) => { data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem) //console.log(queryitem) } + + // Bad code as it doesn't allow for "anything". + if (skipped) { + alert.info("Bad configuration of "+item.name+". Skipping because queries are invalid.") + continue + } } //data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem) if (item.paths.length > 0) { for (querykey in item.paths) { const queryitem = item.paths[querykey] + + if (queryitem.toLowerCase() == "url") { + queryitem = "action_url" + } + + if (queryitem.toLowerCase() == "apikey") { + queryitem = "action_apikey" + } + newitem = { "in": "path", "name": queryitem, @@ -1166,7 +1384,7 @@ const AppCreator = (props) => { } } - if (item.body !== undefined && item.body.length > 0) { + if (item.body !== undefined && item.body !== null && item.body.length > 0) { const required = false newitem = { "in": "body", @@ -1191,21 +1409,38 @@ const AppCreator = (props) => { }, } - /* + data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem) + } else if (actionBodyRequest.includes(item.method.toUpperCase())) { + // Appending an empty field + const required = false + newitem = { + "in": "body", + "name": "body", + "multiline": true, + "description": "Generated by shuffler.io OpenAPI", + "required": required, + "example": "", + "schema": { + "type": "string", + }, + } + + // FIXME - add application/json if JSON example? data.paths[item.url][item.method.toLowerCase()]["requestBody"] = { "description": "Generated by Shuffler.io", "required": required, "content": { "example": { - "example": item.body, + "example": "", }, }, } - */ data.paths[item.url][item.method.toLowerCase()].parameters.push(newitem) + } else { + //console.log("Nothing to append?") } - + // https://swagger.io/docs/specification/describing-request-body/file-upload/ if (item.file_field !== undefined && item.file_field !== null && item.file_field.length > 0) { console.log("HANDLE FILEFIELD SAVE: ", item.file_field) @@ -1237,6 +1472,10 @@ const AppCreator = (props) => { const headersplit = header.split("= ") key = headersplit[0] value = headersplit[1] + } else if (header.length > 0 && header.includes(" =")) { + const headersplit = header.split(" =") + key = headersplit[0] + value = headersplit[1] } else if (header.length > 0 && header.includes("=")) { const headersplit = header.split("=") key = headersplit[0] @@ -1245,6 +1484,10 @@ const AppCreator = (props) => { const headersplit = header.split(": ") key = headersplit[0] value = headersplit[1] + } else if (header.length > 0 && header.includes(" :")) { + const headersplit = header.split(" :") + key = headersplit[0] + value = headersplit[1] } else if (header.length > 0 && header.includes(":")) { const headersplit = header.split(":") key = headersplit[0] @@ -1279,10 +1522,13 @@ const AppCreator = (props) => { return } + var newparamName = parameterName.replaceAll("\"", "") + newparamName = newparamName.replaceAll("\'", "") + data.components.securitySchemes["ApiKeyAuth"] = { "type": "apiKey", "in": parameterLocation.toLowerCase(), - "name": parameterName, + "name": newparamName, } } else if (authenticationOption === "Bearer auth") { data.components.securitySchemes["BearerAuth"] = { @@ -1291,22 +1537,33 @@ const AppCreator = (props) => { "bearerFormat": "UUID", } } else if (authenticationOption === "JWT") { - data.components.securitySchemes["BearerAuth"] = { + data.components.securitySchemes["jwt"] = { "type": "http", "scheme": "bearer", "bearerFormat": "JWT", + "in": parameterName, } + + console.log("SECURITYSCHEMES: ", data.components) } else if (authenticationOption === "Basic auth") { data.components.securitySchemes["BasicAuth"] = { "type": "http", "scheme": "basic", } } else if (authenticationOption === "Oauth2") { + var newparamName = parameterName.replaceAll("\"", "") + newparamName = newparamName.replaceAll("\'", "") + + //parameterName, parameterValue, revocationUrl data.components.securitySchemes["Oauth2"] = { "type": "oauth2", + "description": "Oauth2.0 authorizationCode authentication", "flow": { "authorizationCode": { - + "authorizationUrl": newparamName, + "tokenUrl": parameterLocation, + "refreshUrl": refreshUrl, + "scopes": oauth2Scopes === undefined || oauth2Scopes === null ? [] : oauth2Scopes, }, }, } @@ -1315,6 +1572,13 @@ const AppCreator = (props) => { if (setExtraAuth.length > 0) { for (var key in extraAuth) { const curauth = extraAuth[key] + + if (curauth.name.toLowerCase() == "url") { + alert.error("Can't add extra auth with Name URL") + setAppBuilding(false) + return + } + data.components.securitySchemes[curauth.name] = { "type": "apiKey", "in": curauth.type, @@ -1458,6 +1722,7 @@ const AppCreator = (props) => { } setActions(actions) + setFilteredActions(actions) setUpdate(Math.random()) } @@ -1477,6 +1742,7 @@ const AppCreator = (props) => { }) setActions(actions) + setFilteredActions(actions) setActionAmount(actionAmount-1) setUpdate(Math.random()) } @@ -1596,12 +1862,134 @@ const AppCreator = (props) => { })}
+ const jwtAuth = authenticationOption === "JWT" ? +
+ JWT authentication + + Authentication path + + Must start with / and be a valid path} + onBlur={e => setParameterName(e.target.value)} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style:{ + color: "white", + }, + }} + /> +
+ : + null + const oauth2Auth = authenticationOption === "Oauth2" ?
Oauth2 authentication - - Add the URL to redirect to + + Base Authorization URL + setParameterName(e.target.value)} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style:{ + color: "white", + }, + }} + /> + + Token URL + + setParameterLocation(e.target.value)} + InputProps={{ + classes: { + notchedOutline: classes.notchedOutline, + }, + style:{ + color: "white", + }, + }} + /> + + Refresh-token URL + + setRefreshUrl(e.target.value)} + InputProps={{ + style:{ + color: "white", + }, + }} + /> + + Scopes + + { + oauth2Scopes.push(chip) + console.log(oauth2Scopes) + setOauth2Scopes(oauth2Scopes) + setUpdate(Math.random()) + }} + onDelete={(chip, index) => { + oauth2Scopes.splice(index, 1) + console.log(oauth2Scopes) + setOauth2Scopes(oauth2Scopes) + setUpdate(Math.random()) + }} + />
: null @@ -1621,8 +2009,10 @@ const AppCreator = (props) => { margin="normal" variant="outlined" value={parameterName} - helperText={Can't be empty. Can't contain any of the following characters: !#$%&'^+-._~|]+$} - onChange={e => setParameterName(e.target.value)} + helperText={Can't be empty. Can't contain any of the following characters: !#$%&'^"+-._~|]+$} + onChange={e => { + setParameterName(e.target.value) + }} InputProps={{ classes: { notchedOutline: classes.notchedOutline, @@ -1708,7 +2098,7 @@ const AppCreator = (props) => { null :
- {actions.slice(0,actionAmount).map((data, index) => { + {filteredActions.slice(0,actionAmount).map((data, index) => { var error = data.errors.length > 0 ? @@ -1806,11 +2196,60 @@ const AppCreator = (props) => { } } + const HandleIndividualChip = (props) => { + const { chipData, index } = props; + const [chipRequired, setChipRequired] = useState(false) + + return ( + + { + console.log("CLICK: ", chipData) + setChipRequired(!chipRequired) + }} + /> + + ) + } + + const SetExtraBodyField = (props) => { + const { extraBodyFields } = props; + + if (extraBodyFields === undefined || extraBodyFields === null) { + return null + } + + //const parsedlist = extraBodyFields.join(", ") + //console.log("LIST: ", parsedlist) + + return ( + + {extraBodyFields.map((data, index) => { + return ( + + ) + })} + + ) + } + const bodyInfo = actionBodyRequest.includes(currentActionMethod) ?
Request Body: {extraBodyFields.length > 0 ? - Variables: {extraBodyFields.join(", ")} + : @@ -1896,6 +2335,7 @@ const AppCreator = (props) => { } setActions(actions) + setFilteredActions(actions) } const getActionErrors = () => { @@ -2167,26 +2607,46 @@ const AppCreator = (props) => { }} onBlur={event => { var parsedurl = event.target.value - console.log("URL: ", parsedurl) + //console.log("URL: ", parsedurl) + if (parsedurl.includes(" ")) { + parsedurl = parsedurl.replaceAll(" ", " ") + } + + if (parsedurl.includes(" ")) { + parsedurl = parsedurl.replaceAll(" ", " ") + } + + if (parsedurl.includes("[") && parsedurl.includes("]")) { + //console.log("REPLACE1") + parsedurl = parsedurl.replaceAll("[", "{") + parsedurl = parsedurl.replaceAll("]", "}") + } + if (parsedurl.includes("<") && parsedurl.includes(">")) { - console.log("REPLACE") + //console.log("REPLACE2") parsedurl = parsedurl.replaceAll("<", "{") parsedurl = parsedurl.replaceAll(">", "}") } + //console.log("URL2: ", parsedurl) if (parsedurl.startsWith("PUT ") || parsedurl.startsWith("GET ") ||parsedurl.startsWith("POST ") || parsedurl.startsWith("DELETE ") ||parsedurl.startsWith("PATCH ") || parsedurl.startsWith("CONNECT ")) { + const tmp = parsedurl.split(" ") if (tmp.length > 1) { - parsedurl = tmp[1] + parsedurl = tmp[1].trim() setActionField("url", parsedurl) setCurrentActionMethod(tmp[0].toUpperCase()) setActionField("method", tmp[0].toUpperCase()) } + + console.log("URL3: ", parsedurl) setUpdate(Math.random()) } else if (parsedurl.startsWith("curl")) { + console.log("URL4: ", parsedurl) + const request = parseCurl(event.target.value) if (request !== event.target.value && request.method !== undefined && request.method !== null) { if (request.method.toUpperCase() !== currentAction.Method) { @@ -2452,7 +2912,7 @@ const AppCreator = (props) => { tags: passedTags, execution_org: passedOrg, org_id: passedOrg.id, - id: uuid.v4(), + id: uuidv4(), isValid: true, owner: owner, created: Date.now(), @@ -2668,7 +3128,7 @@ const AppCreator = (props) => { usage: [{ workflow_id: workflow.id, }], - id: uuid.v4(), + id: uuidv4(), active: true, }) @@ -2824,12 +3284,12 @@ const AppCreator = (props) => { const actionView =
- {actionAmount > 0 && actionAmount < actions.length ? + {actionAmount > 0 && actionAmount < filteredActions.length ? : null}
-

Actions {actionAmount > 0 ? ({actionAmount} / {actions.length}) : null}

- Actions are the tasks performed by an app - usually single URL paths for REST API's. + Actions {actionAmount > 0 ? ({actionAmount} / {filteredActions.length}) : null} + + Actions are the tasks performed by an app - usually single URL paths for REST API's. + + + {projectCategories !== undefined && projectCategories !== null && projectCategories.length > 1 ? +
+ {projectCategories.map((tag, index) => { + const newname = tag.charAt(0).toUpperCase() + tag.slice(1) + return ( + { + if (selectedCategory === tag) { + setFilteredActions(actions) + setSelectedCategory("") + setActionAmount(50) + return + } + + const foundActions = actions.filter(data => data.category === tag) + setFilteredActions(foundActions) + setSelectedCategory(tag) + if (actionAmount > foundActions.length) { + setActionAmount(foundActions.length) + } + + //{filteredActions.slice(0,actionAmount).map((data, index) => { + + //console.log("Found: ", foundActions) + //{filteredActions.slice(0,actionAmount).map((data, index) => { + //{actions.slice(0,actionAmount).map((data, index) => { + }} + variant={selectedCategory === tag ? "contained " : "outlined"} + color={selectedCategory === tag ? "primary" : "secondary"} + /> + ) + })} +
+ : null}
{loopActions}
@@ -2902,10 +3412,14 @@ const AppCreator = (props) => { 0, 0, canvas.width, canvas.height ) - const canvasUrl = canvas.toDataURL() - if (canvasUrl !== fileBase64) { - //console.log("SET URL TO: ", canvasUrl) - setFileBase64(canvasUrl) + try { + const canvasUrl = canvas.toDataURL() + if (canvasUrl !== fileBase64) { + //console.log("SET URL TO: ", canvasUrl) + setFileBase64(canvasUrl) + } + } catch (e) { + alert.error("Failed to parse canvasurl!") } } @@ -2915,12 +3429,6 @@ const AppCreator = (props) => { //canvas.height = img.height } - //const imageInfo = file.length === 0 ? - //
- // Upload logo - //
: - // - const [imageUploadError, setImageUploadError] = useState(""); const [openImageModal, setOpenImageModal] = useState(""); const [scale, setScale] = useState(1); @@ -2930,7 +3438,7 @@ const AppCreator = (props) => { let imageData = fileBase64; let croppedData = file.length > 0 ? file : fileBase64 - const imageInfo = + const imageInfo = const alternateImg = { upload.click() @@ -2963,11 +3471,15 @@ const AppCreator = (props) => { const onSaveAppIcon = () => { if(editor){ - setFile(""); - const canvas = editor.getImageScaledToCanvas(); - setFileBase64(canvas.toDataURL()); - setOpenImageModal(false) - setDisableImageUpload(true); + try { + setFile(""); + const canvas = editor.getImageScaledToCanvas(); + setFileBase64(canvas.toDataURL()) + setOpenImageModal(false) + setDisableImageUpload(true); + } catch (e) { + alert.error("Failed to set image. Replace it if this persists.") + } } } @@ -3208,6 +3720,7 @@ const AppCreator = (props) => { {bearerAuth} {apiKey} {oauth2Auth} + {jwtAuth} {extraKeys} {/*authenticationOption === "No authentication" ? null : diff --git a/frontend/src/views/Apps.jsx b/frontend/src/views/Apps.jsx index 9637f1b7..9097e5d2 100644 --- a/frontend/src/views/Apps.jsx +++ b/frontend/src/views/Apps.jsx @@ -9,7 +9,6 @@ import { useTheme } from '@material-ui/core/styles'; import YAML from 'yaml' import {Link} from 'react-router-dom'; -import ReactJson from 'react-json-view' import { useAlert } from "react-alert"; import Dropzone from '../components/Dropzone'; @@ -130,6 +129,7 @@ const Apps = (props) => { const upload = React.useRef(null); const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" ? true : false const borderRadius = 3 + const viewWidth = 590 const { start, stop } = useInterval({ duration: 5000, @@ -178,6 +178,7 @@ const Apps = (props) => { color: "#ffffff", width: "100%", display: "flex", + margin: "auto", } const paperAppStyle = { @@ -474,13 +475,14 @@ const Apps = (props) => { const dividerColor = "rgb(225, 228, 232)" const uploadViewPaperStyle = { - minWidth: 662.5, - maxWidth: 662.5, + minWidth: viewWidth, + maxWidth: viewWidth, color: "white", borderRadius: 5, backgroundColor: surfaceColor, - display: "flex", + //display: "flex", marginBottom: 10, + overflow: "hidden", } const UploadView = () => { @@ -520,7 +522,7 @@ const Apps = (props) => {
) } @@ -707,8 +700,8 @@ const Apps = (props) => { {activateButton} {(props.userdata !== undefined && (props.userdata.role === "admin" || props.userdata.id === selectedApp.owner) || !selectedApp.generated) ?
- {downloadButton} {editButton} + {downloadButton} {deleteButton}
: null} @@ -771,7 +764,7 @@ const Apps = (props) => { {/*

Owner: {selectedApp.owner}

*/} {selectedApp.privateId !== undefined && selectedApp.privateId.length > 0 ?

PrivateID: {selectedApp.privateId}

: null} -
+
{selectedApp.link.length > 0 ?

URL: {selectedApp.link}

: null}
Actions @@ -852,7 +845,7 @@ const Apps = (props) => {

App Creator

How it works  - Security API's -  - OpenAPI directory +  - OpenAPI directory  - OpenAPI Validator
@@ -932,7 +925,11 @@ const Apps = (props) => { console.log("Error in dropzone: ", e) } - reader.readAsText(files[0]); + try { + reader.readAsText(files[0]); + } catch(error) { + alert.error("Failed to read file") + } }; useEffect(() => { @@ -950,9 +947,9 @@ const Apps = (props) => { }, [appValidation, isDropzone]); const appView = isLoggedIn ? - 1366 ? 1366 : 1200, margin: "auto", padding: 20 }} onDrop={uploadFile}> +
-
+
@@ -969,9 +966,9 @@ const Apps = (props) => { : null}
- +
-
+
@@ -980,35 +977,39 @@ const Apps = (props) => {
{isCloud ? null : - - - + {isLoading ? null : + + + + } - } + }
{ : -

+ No apps have been created, uploaded or downloaded yet. Click "Load existing apps" above to get the baseline. This may take a while as its building docker images. -

+ + + If you're still not able to see any apps, please follow our troubleshooting guide for loading apps! +
}
@@ -1571,11 +1575,13 @@ const Apps = (props) => { - + {isCloud ? null : + + } + + + } + {mobile ? null : +
+ } + + {selectedMeta.read_time} minute{selectedMeta.read_time === 1 ? "" : "s"} to read + +
+
+ {mobile || selectedMeta.contributors === undefined || selectedMeta.contributors === null ? "" : +
+ {selectedMeta.contributors.slice(0,7).map((data, index) => { + return ( + + + {data.url} + + + ) + })} +
+ } +
+
+ } + return ( - + { + setHover(true) + }} > {props.level !== 1 ? : null} {element} + {/*hover ? {setHover(true)}} style={{cursor: "pointer", display: "inline", }} onClick={() => { + window.location.href += "#hello" + console.log(window.location) + //window.history.pushState('page2', 'Title', '/page2.php'); + //window.history.replaceState('page2', 'Title', '/page2.php'); + }} /> + : "" + */} + {extraInfo} ) } @@ -234,19 +366,44 @@ const Docs = (props) => { // ); //} - const postDataBrowser = + const postDataBrowser = list === undefined || list === null ? null :
- {list.map((item, index) => { + {list.map((data, index) => { + const item = data.name + if (item === undefined) { + return null + } + const path = "/docs/"+item const newname = item.charAt(0).toUpperCase()+item.substring(1).split("_").join(" ").split("-").join(" ") + const itemMatching = props.match.params.key.toLowerCase() === item.toLowerCase() + //const [tocLines, setTocLines] = React.useState([]); return ( -
  • - {fetchDocs(item)}}> - {newname} +
  • + { + setTocLines([]) + fetchDocs(item) + }}> + > {newname} + {itemMatching && tocLines !== null && tocLines !== undefined && tocLines.length > 0 ? +
    + {tocLines.map((data, index) => { + //console.log(data) + + return ( + {}}> + + - {data.text} + + + ) + })} +
    + : null}
  • ) })} @@ -278,7 +435,7 @@ const Docs = (props) => { flexDirection: "column", } - const postDataMobile = + const postDataMobile = list === undefined || list === null ? null :
    return ( -
    +
    {loadedCheck}
    ) diff --git a/frontend/src/views/LoginPage.jsx b/frontend/src/views/LoginPage.jsx index dac41677..b71313cc 100644 --- a/frontend/src/views/LoginPage.jsx +++ b/frontend/src/views/LoginPage.jsx @@ -1,6 +1,7 @@ /* eslint-disable react/no-multi-comp */ import React, { useState } from 'react'; import { makeStyles } from '@material-ui/styles'; +import { useInterval } from 'react-powerhooks'; import {CircularProgress, TextField, Button, Paper, Typography} from '@material-ui/core' import { useTheme } from '@material-ui/core/styles'; @@ -27,11 +28,13 @@ const useStyles = makeStyles({ const LoginDialog = props => { const theme = useTheme(); - const { globalUrl, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, register } = props; + const { globalUrl, isLoaded, isLoggedIn, setIsLoggedIn, setCookie, register, checkLogin } = props; const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [firstRequest, setFirstRequest] = useState(true); const [loginLoading, setLoginLoading] = useState(false); + const [loginViewLoading, setLoginViewLoading] = useState(false); + const [ssoUrl, setSSOUrl] = useState("") // Used to swap from login to register. True = login, false = register @@ -47,7 +50,6 @@ const LoginDialog = props => { window.location.pathname = "/workflows" } - const checkAdmin = () => { const url = globalUrl + '/api/v1/checkusers'; fetch(url, { @@ -61,6 +63,20 @@ const LoginDialog = props => { if (responseJson["success"] === false) { setLoginInfo(responseJson["reason"]) } else { + if (responseJson.sso_url !== undefined && responseJson.sso_url !== null) { + setSSOUrl(responseJson.sso_url) + } + + if (loginViewLoading) { + setLoginViewLoading(false) + checkLogin() + stop() + + if (responseJson.reason !== undefined && responseJson.reason !== null) { + setLoginInfo(responseJson.reason) + } + } + if (responseJson.reason === "stay") { window.location.pathname = "/adminsetup" } @@ -68,10 +84,21 @@ const LoginDialog = props => { }), ) .catch(error => { - setLoginInfo("Error logging in - please refresh in a minute ", error) + if (!loginViewLoading) { + setLoginViewLoading(true) + start() + } }) } + const { start, stop } = useInterval({ + duration: 3000, + startImmediate: false, + callback: () => { + checkAdmin() + } + }) + if (firstRequest) { setFirstRequest(false) checkAdmin() @@ -178,6 +205,50 @@ const LoginDialog = props => {
    + {loginViewLoading ? +
    + + Waiting for the Shuffle database to become available. This may take up to a minute. + + + {loginInfo === undefined || loginInfo === null || loginInfo.length === 0 ? + null + : +
    + Response: {loginInfo} +
    + } + + + + + + Are you sure Shuffle is installed correctly? + + + 1. Make sure shuffle-database folder has correct access:

    + sudo chown 1000:1000 -R shuffle-database +
    + + + 2. Restart docker-compose:

    + sudo docker-compose restart +
    +
    + + Need help? Join the Discord! + +
    + :

    {formtitle}

    Username @@ -233,14 +304,30 @@ const LoginDialog = props => { />
    -
    {loginInfo}
    + {ssoUrl !== undefined && ssoUrl !== null && ssoUrl.length > 0 ? +
    + + Or + +
    + +
    +
    + : null} + }
    diff --git a/frontend/src/views/SetAuthentication.jsx b/frontend/src/views/SetAuthentication.jsx new file mode 100644 index 00000000..17849a28 --- /dev/null +++ b/frontend/src/views/SetAuthentication.jsx @@ -0,0 +1,147 @@ +import React, {useRef, useState, useEffect, useLayoutEffect} from 'react'; + +import { Typography, CircularProgress } from '@material-ui/core'; + +const SetAuthentication = (props) => { + const { globalUrl, isLoggedIn, isLoaded, userdata } = props; + + const [firstRequest, setFirstRequest] = useState(true) + const [finished, setFinished] = useState(false) + const [response, setResponse] = useState("") + const [failed, setFailed] = useState(false) + + if (firstRequest) { + setFirstRequest(false) + + //code + //session_state + const urlSearchParams = new URLSearchParams(window.location.search); + const params = Object.fromEntries(urlSearchParams.entries()); + + const authenticationStore = [] + var appAuthData = { + "label": "", + "app": { + "name": "", + "id": "", + "app_version": "", + }, + "fields": [], + "type": "oauth2", + } + + if (window !== undefined && window !== null) { + console.log(window.location) + appAuthData.fields.push({"key": "redirect_uri", "value": window.location.origin+window.location.pathname}) + } + + if (params.code !== undefined && params.code !== null) { + appAuthData.fields.push({"key": "code", "value": params.code}) + } + + if (params.session_state !== undefined && params.session_state !== null) { + appAuthData.fields.push({"key": "session_state", "value": params.session_state}) + } + + if (params.state !== undefined && params.state !== null) { + const paramsplit = params.state.split("&") + console.log(paramsplit) + for (var key in paramsplit) { + const query = paramsplit[key].split("=") + console.log(query) + + if (query.length !== 2) { + console.log("INVALID QUERY: ", query) + continue + } + + if (query[0] === "workflow_id") { + appAuthData.reference_workflow = query[1] + } + + if (query[0] === "reference_action_id") { + //appAuthData.ReferenceWorkflow = query[1] + } + + if (query[0] === "app_name") { + appAuthData.app.name = query[1] + appAuthData.label = "Oauth2 for "+query[1] + } + + if (query[0] === "app_id") { + appAuthData.app.id = query[1] + } + + if (query[0] === "app_version") { + appAuthData.app.app_version = query[1] + } + + if (query[0] === "authentication_url") { + appAuthData.fields.push({"key": "authentication_url", "value": query[1]}) + } + + if (query[0] === "scope") { + appAuthData.fields.push({"key": "scope", "value": query[1]}) + } + + if (query[0] === "client_id") { + appAuthData.fields.push({"key": "client_id", "value": query[1]}) + } + + if (query[0] === "client_secret") { + appAuthData.fields.push({"key": "client_secret", "value": query[1]}) + } + + if (query[0] === "oauth_url") { + appAuthData.fields.push({"key": "oauth_url", "value": query[1]}) + } + } + } + + console.log(appAuthData) + + fetch(globalUrl+"/api/v1/apps/authentication", { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + body: JSON.stringify(appAuthData), + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for oauth2 authentication") + setFailed(true) + } + + return response.json() + }) + .then((responseJson) => { + //setUserSettings(responseJson) + console.log("Resp: ", responseJson) + setFinished(true) + setResponse(responseJson.reason) + + setTimeout(() => { + window.close() + }, 1000) + }) + .catch(error => { + console.log(error) + }); + + } + + return ( +
    + + {!finished ? : "DONE WITH AUTH - this will close soon!!"} +
    + {failed ? "Failed setup. Error: " : ""} {response} + +
    + ) +} + +export default SetAuthentication; diff --git a/frontend/src/views/SetAuthenticationSSO.jsx b/frontend/src/views/SetAuthenticationSSO.jsx new file mode 100644 index 00000000..5fcd0f2b --- /dev/null +++ b/frontend/src/views/SetAuthenticationSSO.jsx @@ -0,0 +1,143 @@ +import React, {useRef, useState, useEffect, useLayoutEffect} from 'react'; + +import { Typography, CircularProgress } from '@material-ui/core'; + +const SetAuthentication = (props) => { + const { globalUrl, isLoggedIn, isLoaded, userdata } = props; + + const [firstRequest, setFirstRequest] = useState(true) + const [finished, setFinished] = useState(false) + const [response, setResponse] = useState("") + const [failed, setFailed] = useState(false) + + if (firstRequest) { + setFirstRequest(false) + + //code + //session_state + const urlSearchParams = new URLSearchParams(window.location.search); + const params = Object.fromEntries(urlSearchParams.entries()); + + const authenticationStore = [] + var appAuthData = { + "label": "", + "app": { + "name": "", + "id": "", + "app_version": "", + }, + "fields": [], + "type": "oauth2", + } + + if (window !== undefined && window !== null) { + console.log(window.location) + appAuthData.fields.push({"key": "redirect_uri", "value": window.location.origin+window.location.pathname}) + } + + if (params.code !== undefined && params.code !== null) { + appAuthData.fields.push({"key": "code", "value": params.code}) + } + + if (params.session_state !== undefined && params.session_state !== null) { + appAuthData.fields.push({"key": "session_state", "value": params.session_state}) + } + + if (params.state !== undefined && params.state !== null) { + const paramsplit = params.state.split("&") + console.log(paramsplit) + for (var key in paramsplit) { + const query = paramsplit[key].split("=") + console.log(query) + + if (query.length !== 2) { + console.log("INVALID QUERY: ", query) + continue + } + + if (query[0] === "workflow_id") { + appAuthData.reference_workflow = query[1] + } + + if (query[0] === "reference_action_id") { + //appAuthData.ReferenceWorkflow = query[1] + } + + if (query[0] === "app_name") { + appAuthData.app.name = query[1] + appAuthData.label = "Oauth2 for "+query[1] + } + + if (query[0] === "app_id") { + appAuthData.app.id = query[1] + } + + if (query[0] === "app_version") { + appAuthData.app.app_version = query[1] + } + + if (query[0] === "authentication_url") { + appAuthData.fields.push({"key": "authentication_url", "value": query[1]}) + } + + if (query[0] === "scope") { + appAuthData.fields.push({"key": "scope", "value": query[1]}) + } + + if (query[0] === "client_id") { + appAuthData.fields.push({"key": "client_id", "value": query[1]}) + } + + if (query[0] === "client_secret") { + appAuthData.fields.push({"key": "client_secret", "value": query[1]}) + } + } + } + + console.log(appAuthData) + + fetch(globalUrl+"/api/v1/apps/authentication", { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + }, + credentials: "include", + body: JSON.stringify(appAuthData), + }) + .then((response) => { + if (response.status !== 200) { + console.log("Status not 200 for oauth2 authentication") + setFailed(true) + } + + return response.json() + }) + .then((responseJson) => { + //setUserSettings(responseJson) + console.log("Resp: ", responseJson) + setFinished(true) + setResponse(responseJson.reason) + + setTimeout(() => { + window.close() + }, 1000) + }) + .catch(error => { + console.log(error) + }); + + } + + return ( +
    + + {!finished ? : "DONE WITH AUTH - this will close soon!!"} +
    + {failed ? "Failed setup. Error: " : ""} {response} + +
    + ) +} + +export default SetAuthentication; diff --git a/frontend/src/views/Workflows.jsx b/frontend/src/views/Workflows.jsx index 87c4830e..5805dd78 100644 --- a/frontend/src/views/Workflows.jsx +++ b/frontend/src/views/Workflows.jsx @@ -1,9 +1,11 @@ import React, { useEffect} from 'react'; import { useInterval } from 'react-powerhooks'; import { makeStyles } from '@material-ui/core/styles'; +import { useTheme } from '@material-ui/core/styles'; import {Avatar, Grid, Paper, Tooltip, Divider, Button, TextField, FormControl, IconButton, Menu, MenuItem, FormControlLabel, Chip, Switch, Typography, Zoom, CircularProgress, Dialog, DialogTitle, DialogActions, DialogContent} from '@material-ui/core'; -import {Close as CloseIcon, Compare as CompareIcon, Maximize as MaximizeIcon, Minimize as MinimizeIcon, AddCircle as AddCircleIcon, Toc as TocIcon, Send as SendIcon, Search as SearchIcon, FileCopy as FileCopyIcon, Delete as DeleteIcon, BubbleChart as BubbleChartIcon, Restore as RestoreIcon, Cached as CachedIcon, GetApp as GetAppIcon, Apps as AppsIcon, Edit as EditIcon, MoreVert as MoreVertIcon, PlayArrow as PlayArrowIcon, Add as AddIcon, Publish as PublishIcon, CloudUpload as CloudUploadIcon, CloudDownload as CloudDownloadIcon} from '@material-ui/icons'; +import {GridOn as GridOnIcon, List as ListIcon, Close as CloseIcon, Compare as CompareIcon, Maximize as MaximizeIcon, Minimize as MinimizeIcon, AddCircle as AddCircleIcon, Toc as TocIcon, Send as SendIcon, Search as SearchIcon, FileCopy as FileCopyIcon, Delete as DeleteIcon, BubbleChart as BubbleChartIcon, Restore as RestoreIcon, Cached as CachedIcon, GetApp as GetAppIcon, Apps as AppsIcon, Edit as EditIcon, MoreVert as MoreVertIcon, PlayArrow as PlayArrowIcon, Add as AddIcon, Publish as PublishIcon, CloudUpload as CloudUploadIcon, CloudDownload as CloudDownloadIcon} from '@material-ui/icons'; +import NestedMenuItem from "material-ui-nested-menu-item"; //import {Search as SearchIcon, ArrowUpward as ArrowUpwardIcon, Visibility as VisibilityIcon, Done as DoneIcon, Close as CloseIcon, Error as ErrorIcon, FindReplace as FindreplaceIcon, ArrowLeft as ArrowLeftIcon, Cached as CachedIcon, DirectionsRun as DirectionsRunIcon, Add as AddIcon, Polymer as PolymerIcon, FormatListNumbered as FormatListNumberedIcon, Create as CreateIcon, PlayArrow as PlayArrowIcon, AspectRatio as AspectRatioIcon, MoreVert as MoreVertIcon, Apps as AppsIcon, Schedule as ScheduleIcon, FavoriteBorder as FavoriteBorderIcon, Pause as PauseIcon, Delete as DeleteIcon, AddCircleOutline as AddCircleOutlineIcon, Save as SaveIcon, KeyboardArrowLeft as KeyboardArrowLeftIcon, KeyboardArrowRight as KeyboardArrowRightIcon, ArrowBack as ArrowBackIcon, Settings as SettingsIcon, LockOpen as LockOpenIcon, ExpandMore as ExpandMoreIcon, VpnKey as VpnKeyIcon} from '@material-ui/icons'; //https://next.material-ui.com/components/material-icons/ @@ -11,13 +13,12 @@ import {DataGrid, GridToolbarContainer, GridDensitySelector, GridToolbar} from ' //import JSONPretty from 'react-json-pretty'; //import JSONPrettyMon from 'react-json-pretty/dist/monikai' -import ReactJson from 'react-json-view' import Dropzone from '../components/Dropzone'; import {Link} from 'react-router-dom'; import { useAlert } from "react-alert"; import ChipInput from 'material-ui-chip-input' -import uuid from "uuid" +import { v4 as uuidv4 } from 'uuid'; import CytoscapeWrapper from '../components/RenderCytoscape' //import mobileImage from '../assets/img/mobile.svg'; @@ -27,6 +28,7 @@ import CytoscapeWrapper from '../components/RenderCytoscape' const inputColor = "#383B40" const surfaceColor = "#27292D" const svgSize = 24 +const imagesize = 22 const flexContainerStyle = { display: "flex", @@ -46,13 +48,13 @@ const flexBoxStyle = { } const useStyles = makeStyles((theme) => ({ - root: { + datagrid: { border: 0, '& .MuiDataGrid-columnsContainer': { - backgroundColor: theme.palette.type === 'light' ? '#fafafa' : '#1d1d1d', + backgroundColor: theme.palette.type === 'light' ? '#fafafa' : theme.palette.inputColor, }, '& .MuiDataGrid-iconSeparator': { - display: 'none', + display: 'none', }, '& .MuiDataGrid-colCell, .MuiDataGrid-cell': { borderRight: `1px solid ${ @@ -88,19 +90,19 @@ export const GetIconInfo = (action) => { {"key": "cache_get", "values": ["get_cache"]}, {"key": "filter", "values": ["filter", "route", "router",]}, {"key": "merge", "values": ["join", "merge"]}, - {"key": "search", "values": ["search", "find", "locate", "index",]}, + {"key": "search", "values": ["search", "find", "locate", "index", "analyze", "anal", "match"]}, {"key": "list", "values": ["list", "head", "options"]}, - {"key": "download", "values": ["get", "download", "return", "hello_world", "curl",]}, + {"key": "download", "values": ["capture", "get", "download", "return", "hello_world", "curl", "request", "export", "preview"]}, {"key": "add", "values": ["add"]}, {"key": "delete", "values": ["delete", "remove", "clear", "clean",]}, - {"key": "send", "values": ["send", "dispatch", "mail", "forward", "post", "submit", "mark", "set"]}, - {"key": "repeat", "values": ["repeat", "retry", "pause", "skip",]}, + {"key": "send", "values": ["send", "dispatch", "mail", "forward", "post", "submit", "mark", "set", "release", ]}, + {"key": "repeat", "values": ["repeat", "retry", "pause", "skip", "copy", "replicat", ]}, {"key": "execute", "values": ["execute", "run", "play", "raise",]}, {"key": "extract", "values": ["extract", "unpack", "decompress", "open"]}, {"key": "inflate", "values": ["inflate", "pack", "compress",]}, - {"key": "edit", "values": ["update", "create", "edit", "put", "patch", "change", "replace", "conver", "map", "format", "escape"]}, + {"key": "edit", "values": ["modify", "update", "create", "edit", "put", "patch", "change", "replace", "conver", "map", "format", "escape", "describe", ]}, {"key": "compare", "values": ["compare", "convert", "to", "filter", "translate", "parse"]}, - {"key": "close", "values": ["close", "stop", "cancel",]}, + {"key": "close", "values": ["close", "stop", "cancel", "block", ]}, ] var selectedKey = "" @@ -328,10 +330,11 @@ export const validateJson = (showResult) => { const Workflows = (props) => { const { globalUrl, isLoggedIn, isLoaded, removeCookie, cookies, userdata} = props; document.title = "Shuffle - Workflows" - const referenceUrl = globalUrl+"/api/v1/hooks/" - + const theme = useTheme(); const alert = useAlert() - const classes = useStyles(); + const classes = useStyles(theme); + + const referenceUrl = globalUrl+"/api/v1/hooks/" var upload = "" const [file, setFile] = React.useState(""); @@ -352,11 +355,17 @@ const Workflows = (props) => { const [downloadUrl, setDownloadUrl] = React.useState("https://github.com/frikky/shuffle-workflows") const [downloadBranch, setDownloadBranch] = React.useState("master") const [loadWorkflowsModalOpen, setLoadWorkflowsModalOpen] = React.useState(false) + const [exportModalOpen, setExportModalOpen] = React.useState(false) + const [exportData, setExportData] = React.useState(""); const [modalOpen, setModalOpen] = React.useState(false); const [newWorkflowName, setNewWorkflowName] = React.useState(""); const [newWorkflowDescription, setNewWorkflowDescription] = React.useState(""); const [newWorkflowTags, setNewWorkflowTags] = React.useState([]); + + const [showExtraOptions, setShowExtraOptions] = React.useState(true); + const [defaultReturnValue, setDefaultReturnValue] = React.useState(""); + const [update, setUpdate] = React.useState("test"); const [deleteModalOpen, setDeleteModalOpen] = React.useState(false); const [publishModalOpen, setPublishModalOpen] = React.useState(false); @@ -366,6 +375,8 @@ const Workflows = (props) => { const [isDropzone, setIsDropzone] = React.useState(false); const [view, setView] = React.useState("grid") const [filters, setFilters] = React.useState([]) + const [submitLoading, setSubmitLoading] = React.useState(false) + const isCloud = window.location.host === "localhost:3002" || window.location.host === "shuffler.io" const findWorkflow = (filters) => { @@ -391,6 +402,10 @@ const Workflows = (props) => { return true } else if (curWorkflow.tags.includes(filter)) { return true + } else if (curWorkflow.owner === filter) { + return true + } else if (curWorkflow.org_id === filter) { + return true } else if (curWorkflow.actions !== null && curWorkflow.actions !== undefined) { const newfilter = filter.toLowerCase() for (var key in curWorkflow.actions) { @@ -461,6 +476,50 @@ const Workflows = (props) => { findWorkflow(newfilters) } + const exportVerifyModal = exportModalOpen ? + { + setExportModalOpen(false) + setSelectedWorkflow({}) + }} + PaperProps={{ + style: { + backgroundColor: surfaceColor, + color: "white", + minWidth: 500, + padding: 30, + }, + }} + > + +
    + Want to auto-sanitize this workflow before exporting? +
    +
    + + + This will make potentially sensitive fields such as username, password, url etc. empty + + + + + +
    + : null + const publishModal = publishModalOpen ? { } // Initialize the workflow itself - const ret = setNewWorkflow(data.name, data.description, data.tags, {}, false) + const ret = setNewWorkflow(data.name, data.description, data.tags, data.default_return_value, {}, false) .then((response) => { if (response !== undefined) { // SET THE FULL THING data.id = response.id // Actually create it - const ret = setNewWorkflow(data.name, data.description, data.tags, data, false) + const ret = setNewWorkflow(data.name, data.description, data.tags, data.default_return_value, data, false) .then((response) => { if (response !== undefined) { alert.success(`Successfully imported ${data.name}`) @@ -650,6 +709,11 @@ const Workflows = (props) => { useEffect(() => { if (workflows.length <= 0 && firstrequest) { + const tmpView = localStorage.getItem('view'); + if (tmpView !== undefined && tmpView !== null) { + setView(tmpView) + } + setFirstrequest(false) getAvailableWorkflows() } @@ -759,7 +823,7 @@ const Workflows = (props) => { const exportAllWorkflows = () => { for (var key in workflows) { - exportWorkflow(workflows[key]) + exportWorkflow(workflows[key], false) } } @@ -777,7 +841,7 @@ const Workflows = (props) => { trigger.status = "stopped" } - const newId = uuid.v4() + const newId = uuidv4() if (trigger.trigger_type === "WEBHOOK") { const hookname = "webhook_"+newId if (trigger.parameters !== undefined && trigger.parameters !== null && trigger.parameters.length === 2) { @@ -825,7 +889,7 @@ const Workflows = (props) => { } } - const newId = uuid.v4() + const newId = uuidv4() for (var branchkey in data.branches) { const branch = data.branches[branchkey] if (branch.source_id === data.actions[key].id) { @@ -861,6 +925,7 @@ const Workflows = (props) => { } const sanitizeWorkflow = (data) => { + data = JSON.parse(JSON.stringify(data)) data["owner"] = "" console.log("Sanitize start: ", data) data = deduplicateIds(data) @@ -877,9 +942,13 @@ const Workflows = (props) => { return data } - const exportWorkflow = (data) => { + const exportWorkflow = (data, sanitize) => { + data = JSON.parse(JSON.stringify(data)) let exportFileDefaultName = data.name+'.json'; - data = sanitizeWorkflow(data) + + if (sanitize === true) { + data = sanitizeWorkflow(data) + } //return // Add correct ID's for triggers @@ -1035,7 +1104,6 @@ const Workflows = (props) => { ) } - const WorkflowPaper = (props) => { const { data } = props; const [open, setOpen] = React.useState(false); @@ -1061,20 +1129,114 @@ const Workflows = (props) => { } var parsedName = data.name - if (parsedName !== undefined && parsedName !== null && parsedName.length > 25) { - parsedName = parsedName.slice(0,26)+".." + if (parsedName !== undefined && parsedName !== null && parsedName.length > 20) { + parsedName = parsedName.slice(0,21)+".." } - const actions = data.actions !== null ? data.actions.length : 0 const [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data) + + const workflowMenuButtons = { + setOpen(false) + setAnchorEl(null) + }} + > + { + //console.log("DATA:" ,data) + setModalOpen(true) + setEditingWorkflow(data) + setNewWorkflowName(data.name) + setNewWorkflowDescription(data.description) + setDefaultReturnValue(data.default_return_value) + if (data.tags !== undefined && data.tags !== null) { + setNewWorkflowTags(JSON.parse(JSON.stringify(data.tags))) + } + }} key={"change"}> + + {"Change details"} + + { + setSelectedWorkflow(data) + setPublishModalOpen(true) + }} key={"publish"}> + + {"Publish Workflow"} + + { + copyWorkflow(data) + setOpen(false) + }} key={"duplicate"}> + + {"Duplicate Workflow"} + + = 0} style={{backgroundColor: inputColor, color: "white"}} onClick={() => { + //copyWorkflow(data) + //setOpen(false) + }} key={"duplicate"}> + + {"Copy to Child Org"} + + { + setExportModalOpen(true) + setExportData(data) + setOpen(false) + }} key={"export"}> + + {"Export Workflow"} + + { + setDeleteModalOpen(true) + setSelectedWorkflowId(data.id) + setOpen(false) + }} key={"delete"}> + + {"Delete Workflow"} + + + + var image = "" + var orgName = "" + var orgId = "" + if (userdata.orgs !== undefined) { + const foundOrg = userdata.orgs.find(org => org.id === data["org_id"]) + if (foundOrg !== undefined && foundOrg !== null) { + //position: "absolute", bottom: 5, right: -5, + const imageStyle = {width: imagesize, height: imagesize, pointerEvents: "none", marginRight: 10, marginLeft: data.creator_org !== undefined && data.creator_org.length > 0 ? 20 : 0, borderRadius: 10, border: foundOrg.id === userdata.active_org.id ? `3px solid ${boxColor}` : null, cursor: "pointer", marginRight: 10, } + + // + image = foundOrg.image === "" ? + {foundOrg.name} + : + {foundOrg.name} { + //setFilteredWorkflows(newWorkflows) + }}/> + + orgName = foundOrg.name + orgId = foundOrg.id + } + } + return (
    + +
    { + addFilter(orgId) + //setFilters(["Org "+orgName]) + //setFilteredWorkflows(newWorkflows) + }}> + {image} +
    +
    @@ -1184,59 +1346,7 @@ const Workflows = (props) => { > - { - setOpen(false) - setAnchorEl(null) - }} - > - { - setModalOpen(true) - setEditingWorkflow(data) - setNewWorkflowName(data.name) - setNewWorkflowDescription(data.description) - if (data.tags !== undefined && data.tags !== null) { - setNewWorkflowTags(JSON.parse(JSON.stringify(data.tags))) - } - }} key={"change"}> - - {"Change details"} - - { - setSelectedWorkflow(data) - setPublishModalOpen(true) - }} key={"publish"}> - - {"Publish Workflow"} - - { - copyWorkflow(data) - setOpen(false) - }} key={"duplicate"}> - - {"Duplicate Workflow"} - - { - exportWorkflow(data) - setOpen(false) - }} key={"export"}> - - {"Export Workflow"} - - { - setDeleteModalOpen(true) - setSelectedWorkflowId(data.id) - setOpen(false) - }} key={"delete"}> - - {"Delete Workflow"} - - - + {workflowMenuButtons}
    {/* @@ -1274,184 +1384,14 @@ const Workflows = (props) => { return string.split(search).join(replace); } - const resultsPaper = (data) => { - var boxWidth = "2px" - var boxColor = "orange" - if (data.status === "ABORTED" || data.status === "UNFINISHED" || data.status === "FAILURE"){ - boxColor = "red" - } else if (data.status === "FINISHED" || data.status === "SUCCESS") { - boxColor = "green" - } else if (data.status === "SKIPPED" || data.status === "EXECUTING") { - boxColor = "yellow" - } else { - boxColor = "green" - } - var t = new Date(data.started_at*1000) - var showResult = data.result.trim() - const validate = validateJson(showResult) - - if (validate.valid) { - showResult = - } else { - // FIXME - have everything parsed as json, either just for frontend - // or in the backend? - /* - const newdata = {"result": data.result} - showResult = - */ - } - - return ( - {}}> -
    -
    - - - -

    Name: {data.action.label}

    -
    - - App: {data.action.app_name}, Version: {data.action.app_version} - - - Action: {data.action.name}, Environment: {data.action.environment}, Status: {data.status} - -
    - - Started: {t.toISOString()} - -
    - -
    - - {showResult} - -
    -
    -
    -
    - ) - } - - const resultsHandler = Object.getOwnPropertyNames(selectedExecution).length > 0 && selectedExecution.results !== null ? -
    - {selectedExecution.results.sort((a, b) => a.started_at - b.started_at).map((data, index) => { - return ( -
    - {resultsPaper(data)} -
    - ) - })} -
    - : -
    - No results yet -
    const resultsLength = Object.getOwnPropertyNames(selectedExecution).length > 0 && selectedExecution.results !== null ? selectedExecution.results.length : 0 - const ExecutionDetails = () => { - var starttime = new Date(selectedExecution.started_at*1000) - var endtime = new Date(selectedExecution.started_at*1000) - var parsedArgument = selectedExecution.execution_argument - if (selectedExecution.execution_argument !== undefined && selectedExecution.execution_argument.length > 0) { - parsedArgument = replaceAll(parsedArgument, " None", " \"None\""); - } - - var arg = null - if (selectedExecution.execution_argument !== undefined && selectedExecution.execution_argument.length > 0) { - var showResult = selectedExecution.execution_argument.trim() - const validate = validateJson(showResult) - - arg = validate.valid ? - - : showResult - } - - var lastresult = null - if (selectedExecution.result !== undefined && selectedExecution.result.length > 0) { - var showResult = selectedExecution.result.trim() - const validate = validateJson(showResult) - lastresult = validate.valid ? - - : showResult - } - - /* -
    - ID: {selectedExecution.execution_id} -
    -
    - Last node: {selectedExecution.workflow.actions.find(data => data.id === selectedExecution.last_node).actions[0].label} -
    - */ - if (Object.getOwnPropertyNames(selectedExecution).length > 0 && selectedExecution.workflow.actions !== null) { - return ( -
    -
    - Status: {selectedExecution.status} -
    -
    - Started: {starttime.toISOString()} -
    -
    - Finished: {endtime.toISOString()} -
    - {/* -
    - Last Result: {lastresult} -
    - */} -
    - {arg} -
    - - {resultsHandler} -
    - ) - } - - return ( - executionLoading ? -
    - -
    - : -

    - There are no executiondetails yet. Click "execute" to run your first one. -

    - - ) - } // Can create and set workflows - const setNewWorkflow = (name, description, tags, editingWorkflow, redirect) => { + const setNewWorkflow = (name, description, tags, defaultReturnValue, editingWorkflow, redirect) => { var method = "POST" var extraData = "" @@ -1473,6 +1413,12 @@ const Workflows = (props) => { if (tags !== undefined) { workflowdata["tags"] = tags } + + if (defaultReturnValue !== undefined) { + workflowdata["default_return_value"] = defaultReturnValue + //console.log("WORKFLOW: ", workflowdata) + } + //console.log(workflowdata) //return @@ -1483,26 +1429,31 @@ const Workflows = (props) => { 'Accept': 'application/json', }, body: JSON.stringify(workflowdata), - credentials: "include", - }) + credentials: "include", + }) .then((response) => { if (response.status !== 200) { console.log("Status not 200 for workflows :O!") return } + setSubmitLoading(false) + return response.json() }) .then((responseJson) => { if (method === "POST" && redirect) { window.location.pathname = "/workflows/"+responseJson["id"] + setModalOpen(false) } else if (!redirect) { // Update :) setTimeout(() => { getAvailableWorkflows() }, 1000) setImportLoading(false) + setModalOpen(false) } else { alert.info("Successfully changed basic info for workflow") + setModalOpen(false) } return responseJson @@ -1510,6 +1461,8 @@ const Workflows = (props) => { .catch(error => { alert.error(error.toString()) setImportLoading(false) + setModalOpen(false) + setSubmitLoading(false) }); } @@ -1543,7 +1496,7 @@ const Workflows = (props) => { } // Initialize the workflow itself - const ret = setNewWorkflow(data.name, data.description, data.tags, {}, false) + const ret = setNewWorkflow(data.name, data.description, data.tags, data.default_return_value, {}, false) .then((response) => { if (response !== undefined) { // SET THE FULL THING @@ -1553,7 +1506,7 @@ const Workflows = (props) => { data.is_valid = false // Actually create it - const ret = setNewWorkflow(data.name, data.description, data.tags, data, false) + const ret = setNewWorkflow(data.name, data.description, data.tags, data.default_return_value, data, false) .then((response) => { if (response !== undefined) { alert.success("Successfully imported "+data.name) @@ -1598,48 +1551,143 @@ const Workflows = (props) => { return [triggers, schedules, webhooks, subflows] } - const WorkflowGridView = () => { + const WorkflowListView = () => { let workflowData = ""; if (workflows.length > 0) { const columns = [ - { field: 'title', headerName: 'Title', width: 330, }, - { field: 'actions', headerName: 'Actions', width: 200, sortable: false, + { field: 'image', headerName: 'Logo', width: 42, renderCell: (params) => { + const data = params.row.record + + var boxColor = "#FECC00" + if (data.is_valid) { + boxColor = "#86c142" + } + + if (!data.previously_saved) { + boxColor = "#f85a3e" + } + + var image = "" + var orgName = "" + var orgId = "" + if (userdata.orgs !== undefined) { + const foundOrg = userdata.orgs.find(org => org.id === data["org_id"]) + if (foundOrg !== undefined && foundOrg !== null) { + //position: "absolute", bottom: 5, right: -5, + const imageStyle = {width: imagesize+7, height: imagesize+7, pointerEvents: "none", marginLeft: data.creator_org !== undefined && data.creator_org.length > 0 ? 20 : 0, borderRadius: 10, border: foundOrg.id === userdata.active_org.id ? `3px solid ${boxColor}` : null, cursor: "pointer", marginTop: 5, } + + // + image = foundOrg.image === "" ? + {foundOrg.name} + : + {foundOrg.name} { + //setFilteredWorkflows(newWorkflows) + }}/> + + orgName = foundOrg.name + orgId = foundOrg.id + } + } + + return ( +
    { + //addFilter(orgId) + //setFilters(["Org "+orgName]) + //setFilteredWorkflows(newWorkflows) + }}> + {image} +
    + ) + }}, + { field: 'title', headerName: 'Title', width: 330, renderCell: (params) => { + const data = params.row.record + + return ( + + + + {data.name} + + + + ) + }}, + {/* field: 'category', headerName: 'category', width: 140, renderCell: (params) => { + const data = params.row.record + const category = data.category === undefined ? "not defined" : data.category + return ( + + {category} + + ) + } */}, + { field: 'options', headerName: 'Options', width: 200, sortable: false, disableClickEventBubbling: true, renderCell: (params) => { const data = params.row.record; + const actions = data.actions !== null ? data.actions.length : 0 let [triggers, schedules, webhooks, subflows] = getWorkflowMeta(data); return ( - - - - - - - - executeWorkflow(data.id)} /> - - - - - {webhooks > 0 ? - - +
    + + + + + {actions} + + - : null} - {schedules > 0 ? - - + + + + + {triggers} + + - : null} + + { + if (subflows === 0) { + alert.info("No subflows for "+data.name) + return + } + + var newWorkflows = [data] + for (var key in data.triggers) { + const trigger = data.triggers[key] + if (trigger.app_name !== "Shuffle Workflow") { + continue + } + + if (trigger.parameters !== undefined && trigger.parameters !== null && trigger.parameters.length > 0 && trigger.parameters[0].name === "workflow") { + const newWorkflow = workflows.find(item => item.id === trigger.parameters[0].value) + if (newWorkflow !== null && newWorkflow !== undefined) { + newWorkflows.push(newWorkflow) + continue + } + } + } + + setFilters(["Subflows of "+data.name]) + setFilteredWorkflows(newWorkflows) + }}> + + + + + {subflows} + + + + +
    ) } }, - { field: 'tags', headerName: 'Tags', maxHeight: 15, width: 390, sortable: false, + { field: 'tags', headerName: 'Tags', maxHeight: 15, width: 300, sortable: false, disableClickEventBubbling: true, renderCell: (params) => { const data = params.row.record; @@ -1665,7 +1713,12 @@ const Workflows = (props) => {
    ) } - }, + }, + { field: '', headerName: '', maxHeight: 15, width: 100, sortable: false, + disableClickEventBubbling: true, + renderCell: (params) => { + } + } ]; let rows = []; rows = workflows.map((data, index) => { @@ -1678,9 +1731,19 @@ const Workflows = (props) => { return obj; }); workflowData = - + } return (
    @@ -1726,6 +1789,7 @@ const Workflows = (props) => { placeholder="Name" margin="dense" defaultValue={newWorkflowName} + autoFocus fullWidth /> { color="primary" defaultValue={newWorkflowDescription} placeholder="Description" - rows="6" + rows="3" multiline margin="dense" fullWidth @@ -1764,32 +1828,56 @@ const Workflows = (props) => { setUpdate("delete "+chip) }} /> + {showExtraOptions ? + setDefaultReturnValue(event.target.value)} + InputProps={{ + style:{ + color: "white", + }, + }} + color="primary" + defaultValue={defaultReturnValue} + placeholder="Default return value (used for Subflows if the subflow fails)" + rows="3" + multiline + margin="dense" + fullWidth + /> + : null} - @@ -1820,6 +1908,22 @@ const Workflows = (props) => { : null*/} + {view === "list" && ( + + + + )} + {view === "grid" && ( + + + + )} {importLoading ?
    - {view === "grid" && ( + {view === "grid" ? {filteredWorkflows.map((data, index) => { @@ -1977,11 +2081,9 @@ const Workflows = (props) => { ) })} - )} - - {/*view === "list" && ( - - )*/} + : + + }
    @@ -2027,7 +2129,7 @@ const Workflows = (props) => { return response.json() }) .then((responseJson) => { - console.log("DATA: ", responseJson) + //console.log("DATA: ", responseJson) if (!responseJson.success) { if (responseJson.reason !== undefined) { alert.error("Failed loading: "+responseJson.reason) @@ -2154,6 +2256,8 @@ const Workflows = (props) => { @@ -2167,6 +2271,7 @@ const Workflows = (props) => { {modalView} {deleteModal} + {exportVerifyModal} {publishModal} {workflowDownloadModalOpen}
    diff --git a/functions/extensions/aws-lambda/deploy.sh b/functions/extensions/aws-lambda/deploy.sh deleted file mode 100644 index a3187a27..00000000 --- a/functions/extensions/aws-lambda/deploy.sh +++ /dev/null @@ -1,9 +0,0 @@ -GOOS=linux go build main.go -zip function.zip main - -aws lambda update-function-code \ - --function-name shuffler-forwarder \ - --runtime go1.* \ - --zip-file fileb://function.zip \ - --handler main \ - --role arn:aws:iam::123456789012:role/execution_role diff --git a/functions/extensions/aws-lambda/main.go b/functions/extensions/aws-lambda/main.go deleted file mode 100644 index c78467d6..00000000 --- a/functions/extensions/aws-lambda/main.go +++ /dev/null @@ -1,66 +0,0 @@ -package main - -import ( - "context" - //"encoding/json" - //"fmt" - "github.com/aws/aws-lambda-go/lambda" - "net/http" -) - -type LambdaPayload struct { - RequestContext struct { - Elb struct { - TargetGroupArn string `json:"targetGroupArn"` - } `json:"elb"` - } `json:"requestContext"` - HTTPMethod string `json:"httpMethod"` - Path string `json:"path"` - Headers map[string]string `json:"headers"` - QueryStringParameters map[string]string `json:"queryStringParameters"` - Body string `json:"body"` - IsBase64Encoded bool `json:"isBase64Encoded"` -} - -type LambdaResponse struct { - IsBase64Encoded bool `json:"isBase64Encoded"` - StatusCode int `json:"statusCode"` - StatusDescription string `json:"statusDescription"` - Headers struct { - SetCookie string `json:"Set-cookie"` - ContentType string `json:"Content-Type"` - } `json:"headers"` - Body string `json:"body"` -} - -func lambda_handler(ctx context.Context, payload LambdaPayload) (LambdaResponse, error) { - response := &LambdaResponse{} - response.Headers.ContentType = "text/html" - response.StatusCode = http.StatusBadRequest - response.StatusDescription = http.StatusText(http.StatusBadRequest) - if payload.HTTPMethod == http.MethodGet && payload.Path == "/myfavoritecar" { - res := "TEST" - //car := &Car{} - //car.Model = "Corvette" - //car.Color = "Red" - //car.Year = 1999 - //res, err := json.Marshal(car) - //if err != nil { - // fmt.Println(err) - // response.StatusCode = http.StatusInternalServerError - // response.StatusDescription = http.StatusText(http.StatusInternalServerError) - // return *response, err - //} - response.Headers.ContentType = "application/json" - response.Body = string(res) - response.StatusCode = http.StatusOK - response.StatusDescription = http.StatusText(http.StatusOK) - return *response, nil - } else { - return *response, nil - } -} - -func main() { - lambda.Start(lambda_handler) -} diff --git a/functions/extensions/elasticsearch/run.sh b/functions/extensions/elasticsearch/run.sh new file mode 100644 index 00000000..666b1d95 --- /dev/null +++ b/functions/extensions/elasticsearch/run.sh @@ -0,0 +1,19 @@ +#docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" -e ELASTICSEARCH_USERNAME=frikky -e ELASTICSEARCH_PASSWORD=likeme -e xpack.security.enabled=true docker.elastic.co/elasticsearch/elasticsearch:7.12.1 +#docker run -d -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.12.1 +# +# +# +#echo "\nWaiting for 1.5 minute, then adding data" +#sleep 90 +#echo "\nSlept 90 seconds: ADDING DATA" +#curl -XPOST http://localhost:9200/_security/user/frikky -H "Content-Type: application/json" -d '{"enabled": true, "email": "frikky@shuffler.io"}' + +#curl -XPOST -u frikky:likeme http://localhost:9200/samples/_doc -H "Content-Type: application/json" -d '{"src": "122.14.137.67", "dst": "103.35.191.16", "message": "alert", "md5": "CAEF973033E593C625FB2AA34F7026DC", "sha256": "DB1AEC5222075800EDA75D7205267569679B424E5C58A28102417F46D3B5790D", "hits": 0}' +#echo +#curl -XPOST -u frikky:likeme http://localhost:9200/samples/_doc -H "Content-Type: application/json" -d '{"src": "134.119.219.71", "dst": "103.35.191.41", "message": "alert", "md5": "9498FF82A64FF445398C8426ED63EA5B", "sha256": "8B2E701E91101955C73865589A4C72999AEABC11043F712E05FDB1C17C4AB19A", "hits": 0}' +# +#echo +#curl -XPOST -u frikky:likeme http://localhost:9200/samples2/_doc -H "Content-Type: application/json" -d '{"src": "122.14.137.67", "dst": "103.35.191.16", "message": "alert", "md5": "CAEF973033E593C625FB2AA34F7026DC", "sha256": "DB1AEC5222075800EDA75D7205267569679B424E5C58A28102417F46D3B5790D"}' +#echo +#curl -XPOST -u frikky:likeme http://localhost:9200/samples2/_doc -H "Content-Type: application/json" -d '{"src": "134.119.219.71", "dst": "103.35.191.41", "message": "alert", "md5": "9498FF82A64FF445398C8426ED63EA5B", "sha256": "8B2E701E91101955C73865589A4C72999AEABC11043F712E05FDB1C17C4AB19A"}' +#echo diff --git a/functions/extensions/k8s/shuffle/.helmignore b/functions/extensions/k8s/shuffle/.helmignore new file mode 100644 index 00000000..0e8a0eb3 --- /dev/null +++ b/functions/extensions/k8s/shuffle/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/functions/extensions/k8s/shuffle/Chart.yaml b/functions/extensions/k8s/shuffle/Chart.yaml new file mode 100644 index 00000000..bd42981f --- /dev/null +++ b/functions/extensions/k8s/shuffle/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: shuffle +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "1.16.0" diff --git a/functions/extensions/k8s/shuffle/templates/NOTES.txt b/functions/extensions/k8s/shuffle/templates/NOTES.txt new file mode 100644 index 00000000..eaba4460 --- /dev/null +++ b/functions/extensions/k8s/shuffle/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "shuffle.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "shuffle.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "shuffle.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "shuffle.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/functions/extensions/k8s/shuffle/templates/_helpers.tpl b/functions/extensions/k8s/shuffle/templates/_helpers.tpl new file mode 100644 index 00000000..66efef07 --- /dev/null +++ b/functions/extensions/k8s/shuffle/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "shuffle.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "shuffle.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "shuffle.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "shuffle.labels" -}} +helm.sh/chart: {{ include "shuffle.chart" . }} +{{ include "shuffle.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "shuffle.selectorLabels" -}} +app.kubernetes.io/name: {{ include "shuffle.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "shuffle.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "shuffle.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/functions/extensions/k8s/shuffle/templates/deployment.yaml b/functions/extensions/k8s/shuffle/templates/deployment.yaml new file mode 100644 index 00000000..f2050d82 --- /dev/null +++ b/functions/extensions/k8s/shuffle/templates/deployment.yaml @@ -0,0 +1,253 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.name }}frontend + namespace: {{ .Values.namespace | quote }} +spec: + replicas: 1 + selector: + matchLabels: + service: shuffle + app: shuffle-frontend + template: + metadata: + labels: + service: shuffle + app: shuffle-frontend + spec: + containers: + - name: shuffle-frontend + image: ghcr.io/frikky/shuffle-frontend:nightly + imagePullPolicy: {{ .Values.image.pullPolicy | quote }} + env: + - name: BACKEND_HOSTNAME + value: backend-service + - name: TZ + value: Asia/Shanghai + ports: + - name: http + containerPort: 80 + hostPort: 3001 + - name: https + containerPort: 443 + hostname: shuffle-frontend + restartPolicy: Always + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.name }}backend + namespace: {{ .Values.namespace | quote }} +spec: + replicas: 1 + selector: + matchLabels: + service: shuffle + app: shuffle-backend + template: + metadata: + labels: + service: shuffle + app: shuffle-backend + spec: + containers: + - name: shuffle-backend + image: ghcr.io/frikky/shuffle-backend:nightly + env: + - name: BACKEND_HOSTNAME + value: "backend-service" + - name: BACKEND_PORT + value: "5001" + - name: ENVIRONMENT_NAME + value: "Shuffle" + - name: HTTPS_PROXY + - name: HTTP_PROXY + - name: ORG_ID + value: "Shuffle" + - name: OUTER_HOSTNAME + value: "backend-service" + - name: SHUFFLE_APP_FORCE_UPDATE + value: "false" + - name: SHUFFLE_APP_HOTLOAD_FOLDER + value: "/shuffle-apps" + - name: SHUFFLE_APP_HOTLOAD_LOCATION + value: "/shuffle-apps" + - name: SHUFFLE_CONTAINER_AUTO_CLEANUP + value: "false" + - name: SHUFFLE_DEFAULT_APIKEY + - name: SHUFFLE_DEFAULT_PASSWORD + - name: SHUFFLE_DEFAULT_USERNAME + - name: SHUFFLE_DOWNLOAD_AUTH_BRANCH + - name: SHUFFLE_DOWNLOAD_AUTH_PASSWORD + - name: SHUFFLE_DOWNLOAD_AUTH_USERNAME + - name: SHUFFLE_DOWNLOAD_WORKFLOW_BRANCH + - name: SHUFFLE_DOWNLOAD_WORKFLOW_LOCATION + - name: SHUFFLE_DOWNLOAD_WORKFLOW_PASSWORD + - name: SHUFFLE_DOWNLOAD_WORKFLOW_USERNAME + - name: SHUFFLE_ELASTIC + value: "true" + - name: SHUFFLE_OPENSEARCH_APIKEY + - name: SHUFFLE_OPENSEARCH_CERTIFICATE_FILE + - name: SHUFFLE_OPENSEARCH_CLOUDID + - name: SHUFFLE_OPENSEARCH_PASSWORD + - name: SHUFFLE_OPENSEARCH_PROXY + - name: SHUFFLE_OPENSEARCH_SKIPSSL_VERIFY + value: "true" + - name: SHUFFLE_OPENSEARCH_URL + value: http://opensearch-service:9200 + - name: SHUFFLE_OPENSEARCH_USERNAME + value: "" + - name: SHUFFLE_PASS_APP_PROXY + value: "FALSE" + - name: SHUFFLE_PASS_WORKER_PROXY + value: "FALSE" + volumeMounts: + - mountPath: /var/run/docker.sock + name: docker-sock + - mountPath: /shuffle-apps + name: shuffle-app-hotload-location + - mountPath: /shuffle-files + name: shuffle-file-location + hostname: shuffle-backend + volumes: + - name: docker-sock + hostPath: + path: /var/run/docker.sock + - name: shuffle-app-hotload-location + hostPath: + path: /data/kubernetes/shuffle-apps + type: DirectoryOrCreate + - name: shuffle-file-location + hostPath: + path: /data/kubernetes/shuffle-files + type: DirectoryOrCreate + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ .Values.name }}orborus + namespace: {{ .Values.namespace | quote }} +spec: + replicas: 1 + selector: + matchLabels: + service: shuffle + app: shuffle-orborus + template: + metadata: + labels: + service: shuffle + app: shuffle-orborus + spec: + containers: + - name: shuffle-orborus + image: ghcr.io/frikky/shuffle-orborus:nightly + env: + - name: RUNNING_MODE + value: kubernetes + - name: BASE_URL + value: http://backend-service:5001 + - name: CLEANUP + value: "false" + - name: DOCKER_API_VERSION + value: "1.40" + - name: ENVIRONMENT_NAME + value: Shuffle + - name: HTTPS_PROXY + - name: HTTP_PROXY + - name: ORG_ID + value: Shuffle + - name: SHUFFLE_APP_SDK_VERSION + value: 0.8.97 + - name: SHUFFLE_BASE_IMAGE_NAME + value: frikky + - name: SHUFFLE_BASE_IMAGE_REGISTRY + value: ghcr.io + - name: SHUFFLE_BASE_IMAGE_TAG_SUFFIX + value: "-0.8.80" + - name: SHUFFLE_ORBORUS_EXECUTION_TIMEOUT + value: "600" + - name: SHUFFLE_ORBORUS_EXECUTION_CONCURRENCY + value: "50" + - name: SHUFFLE_PASS_WORKER_PROXY + value: "TRUE" + - name: SHUFFLE_WORKER_VERSION + value: nightly + - name: TZ + value: Asia/Shanghai + volumeMounts: + - mountPath: /var/run/docker.sock + name: docker-sock + hostname: shuffle-orborus + volumes: + - name: docker-sock + hostPath: + path: /var/run/docker.sock +#--- +#apiVersion: apps/v1 +#kind: Deployment +#metadata: +# name: {{ .Values.name }}opensearch +# namespace: {{ .Values.namespace | quote }} +#spec: +# replicas: 1 +# selector: +# matchLabels: +# service: shuffle +# app: shuffle-opensearch +# template: +# metadata: +# labels: +# service: shuffle +# app: shuffle-opensearch +# spec: +# nodeSelector: +# node.bdlab-venus.com/opensearch: available +# initContainers: +# - name: permissions-fix +# image: frikky/busybox +# #volumeMounts: +# # - name: opensearch-claim0 +# # mountPath: /usr/share/elasticsearch/data +# command: [ 'chown' ] +# args: [ '1000:1000', '/usr/share/elasticsearch/data' ] +# containers: +# - name: shuffle-opensearch +# image: opensearchproject/opensearch:1.0.1 +# env: +# - name: TZ +# value: Asia/Shanghai +# - name: bootstrap.memory_lock +# value: "false" +# - name: OPENSEARCH_JAVA_OPTS +# value: "-Xms1024m -Xmx1024m" +# - name: opendistro_security.disabled +# value: "true" +# - name: cluster.routing.allocation.disk.threshold_enabled +# value: "false" +# - name: cluster.name +# value: shuffle-cluster +# - name: node.name +# value: opensearch-service +# - name: discovery.seed_hosts +# value: opensearch-service +# - name: cluster.initial_master_nodes +# value: opensearch-service + # volumeMounts: + # - mountPath: /usr/share/opensearch/data + # name: opensearch-claim0 + #volumes: + # - name: opensearch-claim0 + # persistentVolumeClaim: + # claimName: opensearch-claim0 +# volumeMounts: +# - mountPath: /usr/share/opensearch/data +# readOnly: true +# name: db-location +# volumes: +# - name: db-location +# hostPath: +# path: /data/kubernetes/shuffle-opensearch +# type: DirectoryOrCreate diff --git a/functions/extensions/k8s/shuffle/templates/hpa.yaml b/functions/extensions/k8s/shuffle/templates/hpa.yaml new file mode 100644 index 00000000..49f84ca2 --- /dev/null +++ b/functions/extensions/k8s/shuffle/templates/hpa.yaml @@ -0,0 +1,28 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2beta1 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "shuffle.fullname" . }} + labels: + {{- include "shuffle.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "shuffle.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/functions/extensions/k8s/shuffle/templates/ingress.yaml b/functions/extensions/k8s/shuffle/templates/ingress.yaml new file mode 100644 index 00000000..e35d2ae0 --- /dev/null +++ b/functions/extensions/k8s/shuffle/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "shuffle.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "shuffle.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/functions/extensions/k8s/shuffle/templates/persistent-volume.yaml b/functions/extensions/k8s/shuffle/templates/persistent-volume.yaml new file mode 100644 index 00000000..934c68e2 --- /dev/null +++ b/functions/extensions/k8s/shuffle/templates/persistent-volume.yaml @@ -0,0 +1,24 @@ +#apiVersion: v1 +#kind: PersistentVolume +#metadata: +# name: opensearch-claim0 +# labels: +# app: opensearch-claim0 +#spec: +# capacity: +# storage: "10G" +# volumeMode: Filesystem +# persistentVolumeReclaimPolicy: Retain +# storageClassName: local-storage +# accessModes: +# - "ReadWriteOnce" +# local: +# path: "/data/kubernetes/shuffle-opensearch" +# nodeAffinity: +# required: +# nodeSelectorTerms: +# - matchExpressions: +# - key: node.dollar.com/opensearch +# operator: In +# values: +# - available diff --git a/functions/extensions/k8s/shuffle/templates/persistentvolumeclaim.yaml b/functions/extensions/k8s/shuffle/templates/persistentvolumeclaim.yaml new file mode 100644 index 00000000..7d1736a4 --- /dev/null +++ b/functions/extensions/k8s/shuffle/templates/persistentvolumeclaim.yaml @@ -0,0 +1,17 @@ +#apiVersion: v1 +#kind: PersistentVolumeClaim +#metadata: +# name: opensearch-claim0 +# namespace: {{ .Values.namespace }} +# labels: +# app: opensearch-claim0 +#spec: +# selector: +# matchLabels: +# app: opensearch-claim0 +# accessModes: +# - ReadWriteOnce +# storageClassName: local-storage +# resources: +# requests: +# storage: 5Gi diff --git a/functions/extensions/k8s/shuffle/templates/service.yaml b/functions/extensions/k8s/shuffle/templates/service.yaml new file mode 100644 index 00000000..8fc78dfa --- /dev/null +++ b/functions/extensions/k8s/shuffle/templates/service.yaml @@ -0,0 +1,52 @@ +apiVersion: v1 +kind: Service +metadata: + name: backend-service + namespace: {{ .Values.namespace | quote }} +spec: + ports: + - name: "5001" + port: 5001 + targetPort: 5001 + selector: + app: shuffle-backend + +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend-service + namespace: {{ .Values.namespace | quote }} +spec: + type: NodePort + externalTrafficPolicy: Local + ports: + - name: "3001" + port: 3001 + nodePort: 3001 + targetPort: 80 + - name: "3443" + port: 3443 + nodePort: 3443 + targetPort: 443 + protocol: TCP + selector: + app: shuffle-frontend + +#--- +#apiVersion: v1 +#kind: Service +#metadata: +# name: opensearch-service +# namespace: {{ .Values.namespace | quote }} +#spec: +# type: NodePort +# externalTrafficPolicy: Local +# ports: +# - name: "9200" +# port: 9200 +# targetPort: 9200 +# nodePort: 9200 +# protocol: TCP +# selector: +# app: shuffle-opensearch diff --git a/functions/extensions/k8s/shuffle/templates/serviceaccount.yaml b/functions/extensions/k8s/shuffle/templates/serviceaccount.yaml new file mode 100644 index 00000000..11e9bdb7 --- /dev/null +++ b/functions/extensions/k8s/shuffle/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "shuffle.serviceAccountName" . }} + labels: + {{- include "shuffle.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/functions/extensions/k8s/shuffle/templates/tests/test-connection.yaml b/functions/extensions/k8s/shuffle/templates/tests/test-connection.yaml new file mode 100644 index 00000000..e68f3142 --- /dev/null +++ b/functions/extensions/k8s/shuffle/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "shuffle.fullname" . }}-test-connection" + labels: + {{- include "shuffle.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "shuffle.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/functions/extensions/k8s/shuffle/values.yaml b/functions/extensions/k8s/shuffle/values.yaml new file mode 100644 index 00000000..07e935c6 --- /dev/null +++ b/functions/extensions/k8s/shuffle/values.yaml @@ -0,0 +1,82 @@ +# Default values for shuffle. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 1 + +image: + repository: nginx + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 80 + +ingress: + enabled: false + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: chart-example.local + paths: + - path: / + pathType: ImplementationSpecific + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/functions/extensions/kafka/README.md b/functions/extensions/kafka/README.md deleted file mode 100644 index dceb22a3..00000000 --- a/functions/extensions/kafka/README.md +++ /dev/null @@ -1,4 +0,0 @@ -``` -curl -sSL https://raw.githubusercontent.com/bitnami/bitnami-docker-kafka/master/docker-compose.yml > docker-compose.yml -docker-compose up -d -``` diff --git a/functions/extensions/kafka/docker-compose.yml b/functions/extensions/kafka/docker-compose.yml deleted file mode 100644 index 9a4af72a..00000000 --- a/functions/extensions/kafka/docker-compose.yml +++ /dev/null @@ -1,28 +0,0 @@ -version: "2" - -services: - zookeeper: - image: docker.io/bitnami/zookeeper:3 - ports: - - "2181:2181" - volumes: - - "zookeeper_data:/bitnami" - environment: - - ALLOW_ANONYMOUS_LOGIN=yes - kafka: - image: docker.io/bitnami/kafka:2 - ports: - - "9092:9092" - volumes: - - "kafka_data:/bitnami" - environment: - - KAFKA_CFG_ZOOKEEPER_CONNECT=zookeeper:2181 - - ALLOW_PLAINTEXT_LISTENER=yes - depends_on: - - zookeeper - -volumes: - zookeeper_data: - driver: local - kafka_data: - driver: local diff --git a/functions/extensions/kafka/kafka_local.py b/functions/extensions/kafka/kafka_local.py deleted file mode 100644 index a9e16eb9..00000000 --- a/functions/extensions/kafka/kafka_local.py +++ /dev/null @@ -1,54 +0,0 @@ -import json -import os -import requests -from time import sleep -from kafka import KafkaProducer, KafkaConsumer -import kafka - -shuffle_url = os.getenv("SHUFFLE_URL") -shuffle_apikey = os.getenv("SHUFFLE_APIKEY") -shuffle_workflow = os.getenv("SHUFFLE_WORKFLOW") - -headers = {"Authorization": "Bearer %s" % shuffle_apikey} -topic = "workflow_%s" % shuffle_workflow -server = "localhost:9092" -def produce(): - print("Starting producer") - producer = KafkaProducer( - bootstrap_servers=[server], - value_serializer=lambda x: - json.dumps(x).encode('utf-8') - ) - - print("Adding data!") - for e in range(15): - data = {"some": e, "data": "luuuuul"} - - try: - ret = producer.send(topic, value=data) - print(ret.get()) - except kafka.errors.KafkaTimeoutError as e: - print("Kafka error: %s" % e) - continue - -def consume(): - print("Starting consumer") - consumer = KafkaConsumer( - topic, - bootstrap_servers=[server], - auto_offset_reset="earliest", - enable_auto_commit=True, - value_deserializer=lambda x: json.loads(x.decode('utf-8')) - ) - - print("Getting data") - for message in consumer: - message = message.value - print("MSG: ", message) - ret = requests.post("%s/api/v1/%s/execute" % shuffle_url, headers=headers, data=message) - print(ret.status_code) - print(ret.text) - -if __name__ == "__main__": - produce() - #consume() diff --git a/functions/extensions/kafka/requirements.txt b/functions/extensions/kafka/requirements.txt deleted file mode 100644 index 652ae858..00000000 --- a/functions/extensions/kafka/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -kafka -kafka-python diff --git a/functions/extensions/misp/Dockerfile b/functions/extensions/misp/Dockerfile new file mode 100644 index 00000000..f2b8ae3f --- /dev/null +++ b/functions/extensions/misp/Dockerfile @@ -0,0 +1,23 @@ +FROM python:3.9.4-alpine as base + +FROM base as builder + +RUN mkdir /install +WORKDIR /install + +FROM base +RUN apk add g++ + +COPY --from=builder /install /usr/local +COPY requirements.txt /requirements.txt +RUN pip3 install -r /requirements.txt + + +RUN mkdir /app +WORKDIR /app +COPY requirements.txt /app/requirements.txt +RUN python3 -m pip install -r /app/requirements.txt + +COPY sub.py /app/sub.py + +CMD ["python3", "sub.py"] diff --git a/functions/extensions/misp/docker-compose.yml b/functions/extensions/misp/docker-compose.yml new file mode 100644 index 00000000..5b0130c0 --- /dev/null +++ b/functions/extensions/misp/docker-compose.yml @@ -0,0 +1,9 @@ +version: '3' +services: + zmq: + image: ghcr.io/frikky/shuffle-zmq:latest + environment: + - ZMQ_HOSTNAME=localhost + - ZMQ_PORT=50000 + - ZMQ_FORWARD_URL=https://shuffler.io/api/v1/hooks/webhook_e09bea36-9976-1421-82bc-b8764ca83c1e + restart: unless-stopped diff --git a/functions/extensions/misp/requirements.txt b/functions/extensions/misp/requirements.txt new file mode 100644 index 00000000..a1cdf7dd --- /dev/null +++ b/functions/extensions/misp/requirements.txt @@ -0,0 +1,2 @@ +pyzmq +requests diff --git a/functions/extensions/misp/sub.py b/functions/extensions/misp/sub.py new file mode 100644 index 00000000..feb78c08 --- /dev/null +++ b/functions/extensions/misp/sub.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +print("Running imports") +import sys +import zmq +import json +import time +import pprint +import os +import sys +import requests + +forward_url = os.getenv("ZMQ_FORWARD_URL", "") +print("Checking forward url (ZMQ_FORWARD_URL): %s" % forward_url) +def handle_hook(data): + ret = requests.post(forward_url, json=data) + print(ret.text) + print(ret.status_code) + +def main(): + host = os.getenv("ZMQ_HOST", "localhost") + port = os.getenv("ZMQ_PORT", "50000") + + if len(forward_url) == 0: + print("Failed to start - define ZMQ_FORWARD_URL for webhook forwarder") + exit(0) + + print("Starting connection setup to %s:%s" % (host, port)) + context = zmq.Context() + socket = context.socket(zmq.SUB) + socket.connect ("tcp://%s:%s" % (host, port)) + socket.setsockopt(zmq.SUBSCRIBE, b'') + + poller = zmq.Poller() + poller.register(socket, zmq.POLLIN) + + print("Starting zmq check for %s:%s" % (host, port)) + while True: + socks = dict(poller.poll(timeout=None)) + if socket in socks and socks[socket] == zmq.POLLIN: + message = socket.recv() + #print(message) + topic, s, m = message.decode('utf-8').partition(" ") + + d = json.loads(m) + try: + # print test if you want status (heartbeat) + test = d["status"] + except KeyError: + handle_hook(d) + + time.sleep(1) + +if __name__ == "__main__": + print("In init ") + main() diff --git a/functions/onprem/orborus/Dockerfile b/functions/onprem/orborus/Dockerfile index 6479c09e..b7160a20 100644 --- a/functions/onprem/orborus/Dockerfile +++ b/functions/onprem/orborus/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.16.0-buster as builder +FROM golang:1.17.2-buster as builder RUN mkdir /app WORKDIR /app @@ -15,8 +15,8 @@ RUN go get github.com/docker/docker/api/types && \ RUN go build RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o orborus . -FROM alpine:3.12 -RUN apk add --no-cache bash +FROM alpine:3.14.2 +RUN apk add --no-cache bash tzdata COPY --from=builder /app/ / CMD ["./orborus"] diff --git a/functions/onprem/orborus/build.sh b/functions/onprem/orborus/build.sh index 8a35018c..98bb5f27 100644 --- a/functions/onprem/orborus/build.sh +++ b/functions/onprem/orborus/build.sh @@ -1,5 +1,5 @@ NAME=shuffle-orborus -VERSION=0.8.98 +VERSION=0.9.23 echo "Running docker build with $NAME:$VERSION" #docker rmi frikky/shuffle:$NAME --force diff --git a/functions/onprem/orborus/go.mod b/functions/onprem/orborus/go.mod index 30b558af..f48b58de 100644 --- a/functions/onprem/orborus/go.mod +++ b/functions/onprem/orborus/go.mod @@ -3,21 +3,44 @@ module orborus go 1.13 require ( + cloud.google.com/go/datastore v1.6.0 // indirect + cloud.google.com/go/storage v1.18.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect - github.com/Microsoft/go-winio v0.4.16 // indirect - github.com/algolia/algoliasearch-client-go/v3 v3.18.1 // indirect + github.com/Microsoft/go-winio v0.5.0 // indirect + github.com/algolia/algoliasearch-client-go/v3 v3.21.0 // indirect github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect - github.com/containerd/containerd v1.4.3 // indirect + github.com/containerd/containerd v1.5.7 // indirect + github.com/creack/pty v1.1.16 // indirect github.com/docker/distribution v2.7.1+incompatible // indirect - github.com/docker/docker v20.10.1+incompatible + github.com/docker/docker v20.10.9+incompatible github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect - github.com/frikky/shuffle-shared v0.0.40 - github.com/gogo/protobuf v1.3.1 // indirect - github.com/mackerelio/go-osstat v0.1.0 + github.com/frikky/kin-openapi v0.40.0 // indirect + github.com/frikky/shuffle-shared v0.1.15 + github.com/go-openapi/swag v0.19.15 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/gorilla/mux v1.8.0 // indirect + github.com/kr/pretty v0.3.0 // indirect + github.com/mackerelio/go-osstat v0.2.1 + github.com/mailru/easyjson v0.7.7 // indirect + github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect + github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.1 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/satori/go.uuid v1.2.0 - github.com/sirupsen/logrus v1.7.0 // indirect + go4.org v0.0.0-20201209231011-d4a079459e60 // indirect + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect + golang.org/x/mod v0.5.1 // indirect + golang.org/x/net v0.0.0-20211014172544-2b766c08f1c0 // indirect + golang.org/x/sys v0.0.0-20211013075003-97ac67df715c // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect + golang.org/x/tools v0.1.7 // indirect + google.golang.org/genproto v0.0.0-20211013025323-ce878158c4d4 // indirect + google.golang.org/grpc v1.41.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect ) diff --git a/functions/onprem/orborus/go.sum b/functions/onprem/orborus/go.sum index c5de5ac9..677b4993 100644 --- a/functions/onprem/orborus/go.sum +++ b/functions/onprem/orborus/go.sum @@ -1,3 +1,4 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= @@ -15,8 +16,20 @@ cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOY cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.66.0/go.mod h1:dgqGAjKCDxyhGTtC9dAREQGUJpkceNm1yt590Qno0Ko= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0 h1:XgtDnVJRCPEUG21gjFiRPz4zI1Mjg16R+NYQjfmU4XY= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0 h1:3DXvAyifywvq64LfkKaMOmkWPS1CikIQdMe2lY9vxU8= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= @@ -27,6 +40,8 @@ cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7 cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/datastore v1.4.0 h1:CFDJm15RpYXeEblQ0TMDUrYtqmBmbAWTy536nA8JIc8= cloud.google.com/go/datastore v1.4.0/go.mod h1:d18825/a9bICdAIJy2EkHs9joU4RlIZ1t6l8WDdbdY0= +cloud.google.com/go/datastore v1.6.0 h1:wZaHIqu1tebvGRYhVgcfNX6jN2q638OGO23JyJckxuI= +cloud.google.com/go/datastore v1.6.0/go.mod h1:q3ZJj1GMQRdU0OCv5XXpCqfLqHHZnI5zcumkvuYDmHI= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= @@ -38,44 +53,269 @@ cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RX cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.12.0 h1:4y3gHptW1EHVtcPAVE0eBBlFuGqEejTTG3KdIE0lUX4= cloud.google.com/go/storage v1.12.0/go.mod h1:fFLk2dp2oAhDz8QFKwqrjdJvxSp/W2g7nillojlL5Ho= +cloud.google.com/go/storage v1.18.1 h1:hfXX1gE2mvuwoS/weBfgH1Vr+efX0uLdxX2ETJ9g1JQ= +cloud.google.com/go/storage v1.18.1/go.mod h1:FPalGc7eWSCKf26f2JwjrZhVibDE6pCDP19d1Pe2lB8= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= +github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= +github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= +github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= +github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= +github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= +github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= +github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= github.com/algolia/algoliasearch-client-go v2.25.0+incompatible h1:FGQr9l++u4uQPDXrW8jM5kNJm3Iw5SxEJJtYXSFmPRY= github.com/algolia/algoliasearch-client-go/v3 v3.18.1 h1:FP2Xtqqs/sefR5Qluygp+jVV+juXzEdJaPrZTCDLhDQ= github.com/algolia/algoliasearch-client-go/v3 v3.18.1/go.mod h1:i7tLoP7TYDmHX3Q7vkIOL4syVse/k5VJ+k0i8WqFiJk= +github.com/algolia/algoliasearch-client-go/v3 v3.21.0 h1:rpzkfDdImYGGjKZq+NrovY5vQabD3Fk0DnBtZPk/2jo= +github.com/algolia/algoliasearch-client-go/v3 v3.21.0/go.mod h1:i7tLoP7TYDmHX3Q7vkIOL4syVse/k5VJ+k0i8WqFiJk= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 h1:/P9/RL0xgWE+ehnCUUN5h3RpG3dmoMCOONO1CCvq23Y= github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013/go.mod h1:pccXHIvs3TV/TUqSNyEvF99sxjX2r4FFRIyw6TZY9+w= +github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= +github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= +github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= +github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= +github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= +github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= +github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= +github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= +github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= +github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= +github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= +github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.4.3 h1:ijQT13JedHSHrQGWFcGEwzcNKrAGIiZ+jSD5QQG07SY= github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.11 h1:QCGOUN+i70jEEL/A6JVIbhy4f4fanzAzSR4kNG7SlcE= +github.com/containerd/containerd v1.4.11/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= +github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= +github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= +github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= +github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= +github.com/containerd/containerd v1.5.7 h1:rQyoYtj4KddB3bxG6SAqd4+08gePNyJjRqvOIfV3rkM= +github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= +github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= +github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= +github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= +github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= +github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= +github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= +github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= +github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= +github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= +github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= +github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= +github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= +github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= +github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= +github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= +github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= +github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= +github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= +github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= +github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= +github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.16 h1:vfetlOf3A+9YKggibynnX9mnFjuSVvkRj+IWpcTSLEQ= +github.com/creack/pty v1.1.16/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= +github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= +github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= +github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= +github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v20.10.1+incompatible h1:u0HIBLwOJdemyBdTCkoBX34u3lb5KyBo0rQE3a5Yg+E= github.com/docker/docker v20.10.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.9+incompatible h1:JlsVnETOjM2RLQa0Cc1XCIspUdXW3Zenq9P54uXBm6k= +github.com/docker/docker v20.10.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frikky/go-elasticsearch/v8 v8.13.1 h1:GB+Wr0Yx8efG7D1jc9fGGiqjjRRngWJTcMSua3QIDaM= +github.com/frikky/go-elasticsearch/v8 v8.13.1/go.mod h1:RPq0JXPQVVSFHTlPwj/go8BZ1hegRf+StaSpT2iGIoQ= github.com/frikky/kin-openapi v0.38.0 h1:V7ttwIJS8Vks4KL+mZVj1ZSqhIcQtgaG8akeqXEQgsE= github.com/frikky/kin-openapi v0.38.0/go.mod h1:Fr28TtCHL4K0kIqtqui8HWxN1LG5uAh3z/tDfFyiA1s= +github.com/frikky/kin-openapi v0.40.0 h1:D/b5MvxbQFn6J82Lfw7Gr1CzcNtjH4O6FSY4QuHHYGI= +github.com/frikky/kin-openapi v0.40.0/go.mod h1:ev9OZAw7Bv5p0w93j91++6a1ElPzGcCofst+kmrWsj4= github.com/frikky/shuffle-shared v0.0.12 h1:+0EIfThmK47Po+LogPYZR4XjbS4Ds19WNMFu2YUSjhw= github.com/frikky/shuffle-shared v0.0.12/go.mod h1:SEY432/xs4oBkOUnGwxiYKCZWZLRaheGInhAoW7N8ww= github.com/frikky/shuffle-shared v0.0.23 h1:Pnlc2M6fHnFRLFd5K1iLTVv4/t4P04Ri1GJ5CMxzq0U= @@ -84,22 +324,62 @@ github.com/frikky/shuffle-shared v0.0.28 h1:VQqL3+ePwKSUxCOiCC8DpOEgbb2GhXI8XzFB github.com/frikky/shuffle-shared v0.0.28/go.mod h1:H7SqOta/EAYnfYuWzwzYSh/oWfF0kgnuaJTQNKQBvoQ= github.com/frikky/shuffle-shared v0.0.40 h1:H0au2np5xSy9mZEUWN+a29IORk+YP95FipENBD7iJRw= github.com/frikky/shuffle-shared v0.0.40/go.mod h1:H7SqOta/EAYnfYuWzwzYSh/oWfF0kgnuaJTQNKQBvoQ= +github.com/frikky/shuffle-shared v0.1.15 h1:73XfrsY211Qjdo1RIU7NCl6HMguby0h4QJMUkQx9wQA= +github.com/frikky/shuffle-shared v0.1.15/go.mod h1:w7x1+j3xsAB5T5eeTbPSqs0egVadcoKzYW5GbNdretI= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= +github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= +github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= @@ -107,6 +387,8 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -123,6 +405,14 @@ github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -133,11 +423,27 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo= +github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= @@ -147,57 +453,311 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mackerelio/go-osstat v0.1.0 h1:e57QHeHob8kKJ5FhcXGdzx5O6Ktuc5RHMDIkeqhgkFA= github.com/mackerelio/go-osstat v0.1.0/go.mod h1:1K3NeYLhMHPvzUu+ePYXtoB58wkaRpxZsGClZBJyIFw= +github.com/mackerelio/go-osstat v0.2.1 h1:5AeAcBEutEErAOlDz6WCkEvm6AKYgHTUQrfwm5RbeQc= +github.com/mackerelio/go-osstat v0.2.1/go.mod h1:UzRL8dMCCTqG5WdRtsxbuljMpZt9PCAGXqxPst5QtaY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= +github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= +github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= +github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= +github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= +github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= +github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -205,12 +765,31 @@ go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go4.org v0.0.0-20201209231011-d4a079459e60 h1:iqAGo78tVOJXELHQFRjR6TMwItrvXH4hrGJ32I/NFF8= +go4.org v0.0.0-20201209231011-d4a079459e60/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -235,6 +814,7 @@ golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPI golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= @@ -246,8 +826,14 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= @@ -255,10 +841,16 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -274,10 +866,21 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211014172544-2b766c08f1c0 h1:xNP8gGXzUFztyWFRq+TV6zyPNxOr8lXtV6x0KMrhk0o= +golang.org/x/net v0.0.0-20211014172544-2b766c08f1c0/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -288,6 +891,15 @@ golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210113160501-8b1d76fa0423 h1:/hEknzWkMPCjTo7StMHRrBRa8YBbXuBWfck8680k3RE= golang.org/x/oauth2 v0.0.0-20210113160501-8b1d76fa0423/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 h1:B333XXssMuKQeBwiNODx4TupZy7bf4sxFZnN2ZOcvUE= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -297,27 +909,51 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190410235845-0ad05ae3009d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193GlqGZbnPFnPV/5Rsb4= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -326,13 +962,49 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3 h1:kzM6+9dur93BcC2kVlYl34cHU+TYZLanmpSJHVMmL64= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210113181707-4bcb84eeeb78/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c h1:taxlMj0D/1sOAuv/CbSD+MMDof2vbyPTqz5FNYKpXt8= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -341,9 +1013,19 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -351,11 +1033,14 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -382,6 +1067,7 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= @@ -391,14 +1077,25 @@ golang.org/x/tools v0.0.0-20200915173823-2db8f0ff891c/go.mod h1:z6u4i615ZeAfBE4X golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963 h1:K+NlvTLy0oONtRtkl1jRD9xIhnItbG2PiE7YOdjPb+k= golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= @@ -420,6 +1117,19 @@ google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0 h1:l2Nfbl2GPXdWorv+dT2XfinX2jOOw4zv1VhLstx+6rE= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.58.0 h1:MDkAbYIB1JpSgCTOCYYoIec/coMlKK4oVbpnBLLcyT0= +google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -428,11 +1138,13 @@ google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCID google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= @@ -441,6 +1153,7 @@ google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= @@ -451,6 +1164,7 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= @@ -463,14 +1177,45 @@ google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6D google.golang.org/genproto v0.0.0-20200914193844-75d14daec038/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200921151605-7abf4a1a14d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210113195801-ae06605f4595 h1:x7nk+/4+SvuTDI4wnzQUlhvi+DTpyfncXBo3QWTFs7U= google.golang.org/genproto v0.0.0-20210113195801-ae06605f4595/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211013025323-ce878158c4d4 h1:NBxB1XxiWpGqkPUiJ9PoBXkHV5A9+GohMOA+EmWoPbU= +google.golang.org/genproto v0.0.0-20211013025323-ce878158c4d4/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -481,11 +1226,24 @@ google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.34.1 h1:ugq+9++ZQPFzM2pKUMCIK8gj9M0pFyuUWO9Q8kwEDQw= google.golang.org/grpc v1.34.1/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E= +google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -497,16 +1255,51 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v1.4.0 h1:BjtEgfuw8Qyd+jPvQz8CfoxiO/UjFEidWinwEXZiWv0= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= @@ -514,6 +1307,37 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= +k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= +k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= +k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= +k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= +k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= +k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= +k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= +k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= +k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= +k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= +k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= +k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= +k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= +k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= +k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/functions/onprem/orborus/orborus.go b/functions/onprem/orborus/orborus.go index 9afab33a..eb46a708 100644 --- a/functions/onprem/orborus/orborus.go +++ b/functions/onprem/orborus/orborus.go @@ -51,11 +51,12 @@ var baseimagetagsuffix = os.Getenv("SHUFFLE_BASE_IMAGE_TAG_SUFFIX") var orgId = os.Getenv("ORG_ID") var baseUrl = os.Getenv("BASE_URL") - var environment = os.Getenv("ENVIRONMENT_NAME") var dockerApiVersion = os.Getenv("DOCKER_API_VERSION") var runningMode = strings.ToLower(os.Getenv("RUNNING_MODE")) var cleanupEnv = strings.ToLower(os.Getenv("CLEANUP")) +var timezone = os.Getenv("TZ") +var containerName = os.Getenv("ORBORUS_CONTAINER_NAME") var executionIds = []string{} var dockercli *dockerclient.Client @@ -101,18 +102,25 @@ func getThisContainerId() { if err == nil { containerId = strings.TrimSpace(string(out)) - // cgroup error. Hardcoding this. + // cgroup error. Use fallback strategy below. // https://github.com/moby/moby/issues/7015 //log.Printf("Checking if %s is in %s", ".scope", string(out)) if strings.Contains(string(out), ".scope") { - containerId = "shuffle-orborus" + containerId = "" //docker-76c537e9a4b7c7233011f5d70e6b7f2d600b6413ac58a96519b8dca7a3f7117a.scope } } else { - if fCol == "0" { - containerId = "shuffle-orborus" - log.Printf("[WARNING] Failed getting container ID: %s", err) - } + log.Printf("[WARNING] Failed getting container ID: %s", err) + } + } + + if containerId == "" { + if containerName != "" { + containerId = containerName + log.Printf("[INFO] Falling back to CONTAINER_NAME as container ID") + } else { + containerId = "shuffle-orborus" + log.Printf(`[WARNING] CONTAINER_NAME is not set. Falling back to default name "%s" as container ID`, containerId) } } @@ -137,14 +145,7 @@ func deployWorker(image string, identifier string, env []string) { Binds: []string{ "/var/run/docker.sock:/var/run/docker.sock:rw", }, - } - - // form container id and use it as network source if it's not empty - if containerId != "" { - //log.Printf("[INFO] Found container ID %s", containerId) - hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId)) - } else { - //log.Printf("[INFO] Empty self container id, continue without NetworkMode") + NetworkMode: container.NetworkMode(fmt.Sprintf("container:%s", containerId)), } if cleanupEnv == "true" { @@ -346,6 +347,12 @@ func main() { os.Exit(3) } + if timezone == "" { + timezone = "Europe/Amsterdam" + } + + log.Printf("[INFO] Running with timezone %s", timezone) + workerTimeout := 600 if workerTimeoutEnv != "" { tmpInt, err := strconv.Atoi(workerTimeoutEnv) @@ -560,6 +567,7 @@ func main() { fmt.Sprintf("ENVIRONMENT_NAME=%s", environment), fmt.Sprintf("BASE_URL=%s", baseUrl), fmt.Sprintf("CLEANUP=%s", cleanupEnv), + fmt.Sprintf("TZ=%s", timezone), fmt.Sprintf("SHUFFLE_PASS_APP_PROXY=%s", os.Getenv("SHUFFLE_PASS_APP_PROXY")), } diff --git a/functions/onprem/worker/Dockerfile b/functions/onprem/worker/Dockerfile index 3b8218a1..0dbefd26 100644 --- a/functions/onprem/worker/Dockerfile +++ b/functions/onprem/worker/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.16.0-buster as builder +FROM golang:1.17.2-buster as builder WORKDIR /app #RUN go get github.com/docker/docker/api/types github.com/docker/docker/api/types/container github.com/docker/docker/client @@ -25,13 +25,13 @@ RUN go build RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker . ## ALPINE IMAGE -FROM alpine:3.12 +FROM alpine:3.14.2 ENV SHUFFLE_BASE_IMAGE_REGISTRY=docker.io ENV SHUFFLE_BASE_IMAGE_NAME=frikky/shuffle ENV SHUFFLE_BASE_IMAGE_TAG_SUFFIX=0.8.70 -RUN apk add --no-cache bash +RUN apk add --no-cache bash tzdata COPY --from=builder /app/ / CMD ["./worker"] diff --git a/functions/onprem/worker/build.sh b/functions/onprem/worker/build.sh index ee09f954..779339e7 100644 --- a/functions/onprem/worker/build.sh +++ b/functions/onprem/worker/build.sh @@ -1,5 +1,5 @@ NAME=shuffle-worker -VERSION=0.8.101 +VERSION=0.9.23 echo "Running docker build with $NAME:$VERSION" #CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o worker.bin . diff --git a/functions/onprem/worker/go.mod b/functions/onprem/worker/go.mod index eb62aca7..32284534 100644 --- a/functions/onprem/worker/go.mod +++ b/functions/onprem/worker/go.mod @@ -3,24 +3,47 @@ module worker go 1.15 require ( + cloud.google.com/go/datastore v1.6.0 // indirect + cloud.google.com/go/storage v1.18.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect - github.com/Microsoft/go-winio v0.4.16 // indirect - github.com/algolia/algoliasearch-client-go/v3 v3.18.1 // indirect + github.com/Microsoft/go-winio v0.5.0 // indirect + github.com/algolia/algoliasearch-client-go/v3 v3.21.0 // indirect github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 // indirect - github.com/containerd/containerd v1.4.4 // indirect + github.com/containerd/containerd v1.5.7 // indirect + github.com/creack/pty v1.1.16 // indirect github.com/docker/distribution v2.7.1+incompatible // indirect - github.com/docker/docker v20.10.5+incompatible + github.com/docker/docker v20.10.9+incompatible github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.4.0 // indirect - github.com/frikky/shuffle-shared v0.0.63 + github.com/frikky/kin-openapi v0.40.0 // indirect + github.com/frikky/shuffle-shared v0.1.15 github.com/fsouza/go-dockerclient v1.7.2 github.com/go-git/go-billy/v5 v5.3.1 // indirect + github.com/go-git/go-git/v5 v5.4.2 // indirect + github.com/go-openapi/swag v0.19.15 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/google/go-github/v28 v28.1.1 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/gorilla/mux v1.8.0 + github.com/kr/pretty v0.3.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.1 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 // indirect - google.golang.org/grpc v1.37.1 // indirect + github.com/rogpeppe/go-internal v1.8.0 // indirect + go4.org v0.0.0-20201209231011-d4a079459e60 // indirect + golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect + golang.org/x/mod v0.5.1 // indirect + golang.org/x/net v0.0.0-20211014172544-2b766c08f1c0 // indirect + golang.org/x/sys v0.0.0-20211013075003-97ac67df715c // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect + golang.org/x/tools v0.1.7 // indirect + google.golang.org/genproto v0.0.0-20211013025323-ce878158c4d4 // indirect + google.golang.org/grpc v1.41.0 // indirect + gopkg.in/src-d/go-git.v4 v4.13.1 // indirect ) diff --git a/functions/onprem/worker/go.sum b/functions/onprem/worker/go.sum new file mode 100644 index 00000000..19952995 --- /dev/null +++ b/functions/onprem/worker/go.sum @@ -0,0 +1,1429 @@ +bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.66.0/go.mod h1:dgqGAjKCDxyhGTtC9dAREQGUJpkceNm1yt590Qno0Ko= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0 h1:XgtDnVJRCPEUG21gjFiRPz4zI1Mjg16R+NYQjfmU4XY= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0 h1:3DXvAyifywvq64LfkKaMOmkWPS1CikIQdMe2lY9vxU8= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/datastore v1.4.0 h1:CFDJm15RpYXeEblQ0TMDUrYtqmBmbAWTy536nA8JIc8= +cloud.google.com/go/datastore v1.4.0/go.mod h1:d18825/a9bICdAIJy2EkHs9joU4RlIZ1t6l8WDdbdY0= +cloud.google.com/go/datastore v1.6.0 h1:wZaHIqu1tebvGRYhVgcfNX6jN2q638OGO23JyJckxuI= +cloud.google.com/go/datastore v1.6.0/go.mod h1:q3ZJj1GMQRdU0OCv5XXpCqfLqHHZnI5zcumkvuYDmHI= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.12.0 h1:4y3gHptW1EHVtcPAVE0eBBlFuGqEejTTG3KdIE0lUX4= +cloud.google.com/go/storage v1.12.0/go.mod h1:fFLk2dp2oAhDz8QFKwqrjdJvxSp/W2g7nillojlL5Ho= +cloud.google.com/go/storage v1.18.1 h1:hfXX1gE2mvuwoS/weBfgH1Vr+efX0uLdxX2ETJ9g1JQ= +cloud.google.com/go/storage v1.18.1/go.mod h1:FPalGc7eWSCKf26f2JwjrZhVibDE6pCDP19d1Pe2lB8= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= +github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= +github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/go-winio v0.5.0 h1:Elr9Wn+sGKPlkaBvwu4mTrxtmOp3F3yV9qhaHbXGjwU= +github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= +github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= +github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= +github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= +github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= +github.com/Microsoft/hcsshim v0.8.21/go.mod h1:+w2gRZ5ReXQhFOrvSQeNfhrYB/dg3oDwTOcER2fw4I4= +github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= +github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= +github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= +github.com/algolia/algoliasearch-client-go v2.25.0+incompatible h1:FGQr9l++u4uQPDXrW8jM5kNJm3Iw5SxEJJtYXSFmPRY= +github.com/algolia/algoliasearch-client-go/v3 v3.18.1 h1:FP2Xtqqs/sefR5Qluygp+jVV+juXzEdJaPrZTCDLhDQ= +github.com/algolia/algoliasearch-client-go/v3 v3.18.1/go.mod h1:i7tLoP7TYDmHX3Q7vkIOL4syVse/k5VJ+k0i8WqFiJk= +github.com/algolia/algoliasearch-client-go/v3 v3.21.0 h1:rpzkfDdImYGGjKZq+NrovY5vQabD3Fk0DnBtZPk/2jo= +github.com/algolia/algoliasearch-client-go/v3 v3.21.0/go.mod h1:i7tLoP7TYDmHX3Q7vkIOL4syVse/k5VJ+k0i8WqFiJk= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= +github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= +github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= +github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013 h1:/P9/RL0xgWE+ehnCUUN5h3RpG3dmoMCOONO1CCvq23Y= +github.com/bradfitz/slice v0.0.0-20180809154707-2b758aa73013/go.mod h1:pccXHIvs3TV/TUqSNyEvF99sxjX2r4FFRIyw6TZY9+w= +github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= +github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= +github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= +github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= +github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= +github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= +github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= +github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= +github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= +github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= +github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= +github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= +github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= +github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= +github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= +github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= +github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= +github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= +github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= +github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= +github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= +github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.4 h1:rtRG4N6Ct7GNssATwgpvMGfnjnwfjnu/Zs9W3Ikzq+M= +github.com/containerd/containerd v1.4.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.4.11 h1:QCGOUN+i70jEEL/A6JVIbhy4f4fanzAzSR4kNG7SlcE= +github.com/containerd/containerd v1.4.11/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= +github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= +github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= +github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= +github.com/containerd/containerd v1.5.1/go.mod h1:0DOxVqwDy2iZvrZp2JUx/E+hS0UNTVn7dJnIOwtYR4g= +github.com/containerd/containerd v1.5.7 h1:rQyoYtj4KddB3bxG6SAqd4+08gePNyJjRqvOIfV3rkM= +github.com/containerd/containerd v1.5.7/go.mod h1:gyvv6+ugqY25TiXxcZC3L5yOeYgEw0QMhscqVp1AR9c= +github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= +github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= +github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e h1:6JKvHHt396/qabvMhnhUZvWaHZzfVfldxE60TK8YLhg= +github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= +github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= +github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= +github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= +github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= +github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= +github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= +github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= +github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= +github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= +github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= +github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= +github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= +github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= +github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= +github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= +github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= +github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= +github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= +github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= +github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= +github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= +github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= +github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= +github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= +github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= +github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= +github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= +github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= +github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= +github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.16 h1:vfetlOf3A+9YKggibynnX9mnFjuSVvkRj+IWpcTSLEQ= +github.com/creack/pty v1.1.16/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= +github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= +github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= +github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= +github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= +github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= +github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= +github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v1.13.1 h1:IkZjBSIc8hBjLpqeAbeE5mca5mNgeatLHBy3GO78BWo= +github.com/docker/docker v20.10.3-0.20210216175712-646072ed6524+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.5+incompatible h1:o5WL5onN4awYGwrW7+oTn5x9AF2prw7V0Ox8ZEkoCdg= +github.com/docker/docker v20.10.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v20.10.9+incompatible h1:JlsVnETOjM2RLQa0Cc1XCIspUdXW3Zenq9P54uXBm6k= +github.com/docker/docker v20.10.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= +github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= +github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elastic/go-elasticsearch v0.0.0 h1:Pd5fqOuBxKxv83b0+xOAJDAkziWYwFinWnBO0y+TZaA= +github.com/elastic/go-elasticsearch/v8 v8.0.0-20210531084204-f01628963386 h1:r0QXRPqcbciA/BJPhNE4kd5tEJR1AhyqoBXsV452XA4= +github.com/elastic/go-elasticsearch/v8 v8.0.0-20210531084204-f01628963386/go.mod h1:xe9a/L2aeOgFKKgrO3ibQTnMdpAeL0GC+5/HpGScSa4= +github.com/elastic/go-elasticsearch/v8 v8.0.0-20210608143047-aa1301e7ba9d h1:id0CyeIuvJ9hzYhLlKsXHQ10d/k4G+CexAu53Pl3hf4= +github.com/elastic/go-elasticsearch/v8 v8.0.0-20210608143047-aa1301e7ba9d/go.mod h1:xe9a/L2aeOgFKKgrO3ibQTnMdpAeL0GC+5/HpGScSa4= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frikky/go-elasticsearch/v8 v8.13.1 h1:GB+Wr0Yx8efG7D1jc9fGGiqjjRRngWJTcMSua3QIDaM= +github.com/frikky/go-elasticsearch/v8 v8.13.1/go.mod h1:RPq0JXPQVVSFHTlPwj/go8BZ1hegRf+StaSpT2iGIoQ= +github.com/frikky/kin-openapi v0.38.0 h1:V7ttwIJS8Vks4KL+mZVj1ZSqhIcQtgaG8akeqXEQgsE= +github.com/frikky/kin-openapi v0.38.0/go.mod h1:Fr28TtCHL4K0kIqtqui8HWxN1LG5uAh3z/tDfFyiA1s= +github.com/frikky/kin-openapi v0.40.0 h1:D/b5MvxbQFn6J82Lfw7Gr1CzcNtjH4O6FSY4QuHHYGI= +github.com/frikky/kin-openapi v0.40.0/go.mod h1:ev9OZAw7Bv5p0w93j91++6a1ElPzGcCofst+kmrWsj4= +github.com/frikky/shuffle-shared v0.0.20 h1:y6JlPnQDq//elICWvVfUfJyU9gH3fSpQmPy+agqZ5sA= +github.com/frikky/shuffle-shared v0.0.20/go.mod h1:H7SqOta/EAYnfYuWzwzYSh/oWfF0kgnuaJTQNKQBvoQ= +github.com/frikky/shuffle-shared v0.0.24 h1:AqaeKE+lUN9G2pfdFKlXD1ImAzcMjcpuGMSqjt15RIk= +github.com/frikky/shuffle-shared v0.0.24/go.mod h1:H7SqOta/EAYnfYuWzwzYSh/oWfF0kgnuaJTQNKQBvoQ= +github.com/frikky/shuffle-shared v0.0.28 h1:VQqL3+ePwKSUxCOiCC8DpOEgbb2GhXI8XzFB/YlHbps= +github.com/frikky/shuffle-shared v0.0.28/go.mod h1:H7SqOta/EAYnfYuWzwzYSh/oWfF0kgnuaJTQNKQBvoQ= +github.com/frikky/shuffle-shared v0.0.40 h1:H0au2np5xSy9mZEUWN+a29IORk+YP95FipENBD7iJRw= +github.com/frikky/shuffle-shared v0.0.40/go.mod h1:H7SqOta/EAYnfYuWzwzYSh/oWfF0kgnuaJTQNKQBvoQ= +github.com/frikky/shuffle-shared v0.0.41 h1:kA+Hm9YXYVTkvJhIPXmETJu9KKxBXKvn3ksTCHJ5688= +github.com/frikky/shuffle-shared v0.0.41/go.mod h1:H7SqOta/EAYnfYuWzwzYSh/oWfF0kgnuaJTQNKQBvoQ= +github.com/frikky/shuffle-shared v0.0.44 h1:w2tp3UJDaIL0/1xVxUBAyq8zqW29ImqdtIt2J44LjgU= +github.com/frikky/shuffle-shared v0.0.44/go.mod h1:BknTfpun3qte5bumR3OqQHf9XWPIsyj8woiXCjIlbBc= +github.com/frikky/shuffle-shared v0.0.56 h1:stC793SdQeBh98yJqCL74aXFo9YYhVIg8CQJ7hm4d6o= +github.com/frikky/shuffle-shared v0.0.56/go.mod h1:BknTfpun3qte5bumR3OqQHf9XWPIsyj8woiXCjIlbBc= +github.com/frikky/shuffle-shared v0.0.57 h1:YDlOVjg8bUBcinehcmorxzOoV5O55oUfG8q7TOiBOeU= +github.com/frikky/shuffle-shared v0.0.57/go.mod h1:BknTfpun3qte5bumR3OqQHf9XWPIsyj8woiXCjIlbBc= +github.com/frikky/shuffle-shared v0.0.59 h1:kU5nAiaLJu2EQeb6yp7cVd94LkoU1YAiYQsQSBXLWHM= +github.com/frikky/shuffle-shared v0.0.59/go.mod h1:BknTfpun3qte5bumR3OqQHf9XWPIsyj8woiXCjIlbBc= +github.com/frikky/shuffle-shared v0.0.63 h1:btn7V7s98eZmx/9qyapGE3V1TyxftvjMmrzkEcUvu+c= +github.com/frikky/shuffle-shared v0.0.63/go.mod h1:oPDyGFBuurPtE6UQLUrHU/JtbOJftqDljRfEdLlCTI4= +github.com/frikky/shuffle-shared v0.0.85 h1:0Xs1omjCzBKCxk6RZPrqyHx0UIuizMAEtAmPjplGhIY= +github.com/frikky/shuffle-shared v0.0.85/go.mod h1:pUK3pKKOFtu4mPJ68c396WqXJF9YTKWKtsAy871HdGY= +github.com/frikky/shuffle-shared v0.0.88 h1:KmP41FLWUlOw5E954LYrArJy/QMiviUQw/5LxSKqzqo= +github.com/frikky/shuffle-shared v0.0.88/go.mod h1:pUK3pKKOFtu4mPJ68c396WqXJF9YTKWKtsAy871HdGY= +github.com/frikky/shuffle-shared v0.0.95 h1:Uox2d1ZTjtG/tM54sJwQSzmxhTU387PDzzCYrwgTmZE= +github.com/frikky/shuffle-shared v0.0.95/go.mod h1:w7x1+j3xsAB5T5eeTbPSqs0egVadcoKzYW5GbNdretI= +github.com/frikky/shuffle-shared v0.1.15 h1:73XfrsY211Qjdo1RIU7NCl6HMguby0h4QJMUkQx9wQA= +github.com/frikky/shuffle-shared v0.1.15/go.mod h1:w7x1+j3xsAB5T5eeTbPSqs0egVadcoKzYW5GbNdretI= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsouza/go-dockerclient v1.7.2 h1:bBEAcqLTkpq205jooP5RVroUKiVEWgGecHyeZc4OFjo= +github.com/fsouza/go-dockerclient v1.7.2/go.mod h1:+ugtMCVRwnPfY7d8/baCzZ3uwB0BrG5DB8OzbtxaRz8= +github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= +github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy v1.0.0 h1:bXR6Zu3opPSg0R4dDxqaLglY4rxw7ja7wS16qSpOKL4= +github.com/go-git/go-billy v4.2.0+incompatible h1:Z6QtVXd5tjxUtcODLugkJg4WaZnGg13CD8qB9pr+7q0= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= +github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git v1.0.0 h1:YcN9iDGDoXuIw0vHls6rINwV416HYa0EB2X+RBsyYp4= +github.com/go-git/go-git v4.7.0+incompatible h1:+W9rgGY4DOKKdX2x6HxSR7HNeTxqiKrOvKnuittYVdA= +github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= +github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= +github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= +github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= +github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= +github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= +github.com/google/go-github/v28 v28.1.1 h1:kORf5ekX5qwXO2mGzXXOjMe/g6ap8ahVe0sBEulhSxo= +github.com/google/go-github/v28 v28.1.1/go.mod h1:bsqJWQX05omyWVmc00nEUql9mhQyv38lDZ8kPZcQVoM= +github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0 h1:wCKgOCHuUEVfsaQLpPSJb7VdYCdTVZQAuOdYm1yc/60= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200905233945-acf8798be1f7/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/sys/mount v0.2.0 h1:WhCW5B355jtxndN5ovugJlMFJawbUODuW8fSnEH6SSM= +github.com/moby/sys/mount v0.2.0/go.mod h1:aAivFE2LB3W4bACsUXChRHQ0qKWsetY4Y9V7sxOougM= +github.com/moby/sys/mountinfo v0.4.0 h1:1KInV3Huv18akCu58V7lzNlt+jFmqlu1EaErnEHE/VM= +github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= +github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk= +github.com/moby/term v0.0.0-20201216013528-df9cb8a40635/go.mod h1:FBS0z0QWA44HXygs7VXDUOGoN/1TV3RuWkLO04am3wc= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= +github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= +github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v0.1.1 h1:GlxAyO6x8rfZYN9Tt0Kti5a/cP41iuiO2yYT0IJGY8Y= +github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= +github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= +github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= +github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= +github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= +github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= +github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= +github.com/patrickmn/go-cache v1.0.0 h1:3gD5McaYs9CxjyK5AXGcq8gdeCARtd/9gJDUvVeaZ0Y= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= +github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= +github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= +github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= +github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= +github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= +github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= +github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= +github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= +github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= +github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= +github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= +github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= +github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= +github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= +github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= +go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go4.org v0.0.0-20201209231011-d4a079459e60 h1:iqAGo78tVOJXELHQFRjR6TMwItrvXH4hrGJ32I/NFF8= +go4.org v0.0.0-20201209231011-d4a079459e60/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg= +golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210326060303-6b1517762897 h1:KrsHThm5nFk34YtATK1LsThyGhGbGe1olrte/HInHvs= +golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211014172544-2b766c08f1c0 h1:xNP8gGXzUFztyWFRq+TV6zyPNxOr8lXtV6x0KMrhk0o= +golang.org/x/net v0.0.0-20211014172544-2b766c08f1c0/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210113160501-8b1d76fa0423 h1:/hEknzWkMPCjTo7StMHRrBRa8YBbXuBWfck8680k3RE= +golang.org/x/oauth2 v0.0.0-20210113160501-8b1d76fa0423/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1 h1:B333XXssMuKQeBwiNODx4TupZy7bf4sxFZnN2ZOcvUE= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3 h1:kzM6+9dur93BcC2kVlYl34cHU+TYZLanmpSJHVMmL64= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210216224549-f992740a1bac h1:9glrpwtNjBYgRpb67AZJKHfzj1stG/8BL5H7In2oTC4= +golang.org/x/sys v0.0.0-20210216224549-f992740a1bac/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79 h1:RX8C8PRZc2hTIod4ds8ij+/4RQX3AqhYj3uOHmyaz4E= +golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c h1:taxlMj0D/1sOAuv/CbSD+MMDof2vbyPTqz5FNYKpXt8= +golang.org/x/sys v0.0.0-20211013075003-97ac67df715c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201113234701-d7a72108b828/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200828161849-5deb26317202/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20200915173823-2db8f0ff891c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20200918232735-d647fc253266/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963 h1:K+NlvTLy0oONtRtkl1jRD9xIhnItbG2PiE7YOdjPb+k= +golang.org/x/tools v0.0.0-20210114065538-d78b04bdf963/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.31.0/go.mod h1:CL+9IBCa2WWU6gRuBWaKqGWLFFwbEUXkfeMkHLQWYWo= +google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0 h1:l2Nfbl2GPXdWorv+dT2XfinX2jOOw4zv1VhLstx+6rE= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.58.0 h1:MDkAbYIB1JpSgCTOCYYoIec/coMlKK4oVbpnBLLcyT0= +google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200831141814-d751682dd103/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200914193844-75d14daec038/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200921151605-7abf4a1a14d5/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210113195801-ae06605f4595 h1:x7nk+/4+SvuTDI4wnzQUlhvi+DTpyfncXBo3QWTFs7U= +google.golang.org/genproto v0.0.0-20210113195801-ae06605f4595/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211013025323-ce878158c4d4 h1:NBxB1XxiWpGqkPUiJ9PoBXkHV5A9+GohMOA+EmWoPbU= +google.golang.org/genproto v0.0.0-20211013025323-ce878158c4d4/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.34.1 h1:ugq+9++ZQPFzM2pKUMCIK8gj9M0pFyuUWO9Q8kwEDQw= +google.golang.org/grpc v1.34.1/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1 h1:ARnQJNWxGyYJpdf/JXscNlQr/uv607ZPU9Z7ogHi+iI= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E= +google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= +gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= +gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE= +gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= +k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= +k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= +k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= +k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= +k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= +k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= +k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= +k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= +k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= +k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= +k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= +k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= +k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= +k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= +k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= +k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/functions/onprem/worker/worker.go b/functions/onprem/worker/worker.go index 876b4859..0db54ce0 100644 --- a/functions/onprem/worker/worker.go +++ b/functions/onprem/worker/worker.go @@ -16,7 +16,6 @@ import ( "net/http" "net/url" "os" - "os/exec" "strings" "time" @@ -39,9 +38,9 @@ var environment = os.Getenv("ENVIRONMENT_NAME") var baseUrl = os.Getenv("BASE_URL") var appCallbackUrl = os.Getenv("BASE_URL") var cleanupEnv = strings.ToLower(os.Getenv("CLEANUP")) +var timezone = os.Getenv("TZ") var baseimagename = "frikky/shuffle" var registryName = "registry.hub.docker.com" -var fallbackName = "shuffle-orborus" var sleepTime = 2 var requestCache *cache.Cache var topClient *http.Client @@ -60,38 +59,6 @@ var startAction string var results []shuffle.ActionResult var allLogs map[string]string -var containerId string - -// form container id of current running container -func getThisContainerId() string { - if len(containerId) > 0 { - return containerId - } - - id := "" - cmd := fmt.Sprintf("cat /proc/self/cgroup | grep memory | tail -1 | cut -d/ -f3 | grep -o -E '[0-9A-z]{64}'") - out, err := exec.Command("bash", "-c", cmd).Output() - if err == nil { - id = strings.TrimSpace(string(out)) - - //log.Printf("Checking if %s is in %s", ".scope", string(out)) - if strings.Contains(string(out), ".scope") { - id = fallbackName - } - } - - return id -} - -func init() { - containerId = getThisContainerId() - if len(containerId) == 0 { - log.Printf("[WARNING] No container ID found. Not running containerized? This should only show during testing") - } else { - log.Printf("[INFO] Found container ID for this worker: %s", containerId) - } -} - // removes every container except itself (worker) func shutdown(workflowExecution shuffle.WorkflowExecution, nodeId string, reason string, handleResultSend bool) { log.Printf("[INFO] Shutdown (%s) started with reason %#v. Result amount: %d. ResultsSent: %d, Send result: %#v", workflowExecution.Status, reason, len(workflowExecution.Results), requestsSent, handleResultSend) @@ -134,62 +101,67 @@ func shutdown(workflowExecution shuffle.WorkflowExecution, nodeId string, reason log.Printf("[INFO] NOT cleaning up containers. IDS: %d, CLEANUP env: %s", len(containerIds), cleanupEnv) } - abortUrl := fmt.Sprintf("%s/api/v1/workflows/%s/executions/%s/abort", baseUrl, workflowExecution.Workflow.ID, workflowExecution.ExecutionId) + if len(reason) > 0 && len(nodeId) > 0 { + log.Printf("[INFO] Running abort of workflow because it should be finished") - path := fmt.Sprintf("?reason=%s", url.QueryEscape(reason)) - if len(nodeId) > 0 { - path += fmt.Sprintf("&node=%s", url.QueryEscape(nodeId)) - } - if len(environment) > 0 { - path += fmt.Sprintf("&env=%s", url.QueryEscape(environment)) - } - - //fmt.Println(url.QueryEscape(query)) - abortUrl += path - log.Printf("[INFO] Abort URL: %s", abortUrl) - - req, err := http.NewRequest( - "GET", - abortUrl, - nil, - ) - - if err != nil { - log.Println("[INFO] Failed building request: %s", err) - } - - // FIXME: Add an API call to the backend - authorization := os.Getenv("AUTHORIZATION") - if len(authorization) > 0 { - req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", authorization)) - } else { - log.Printf("[ERROR] No authorization specified for abort") - } - - req.Header.Add("Content-Type", "application/json") - client := &http.Client{ - Transport: &http.Transport{ - Proxy: nil, - }, - } - - httpProxy := os.Getenv("HTTP_PROXY") - httpsProxy := os.Getenv("HTTPS_PROXY") - if (len(httpProxy) > 0 || len(httpsProxy) > 0) && baseUrl != "http://shuffle-backend:5001" { - client = &http.Client{} - } else { - if len(httpProxy) > 0 { - log.Printf("[INFO] Running with HTTP proxy %s (env: HTTP_PROXY)", httpProxy) + abortUrl := fmt.Sprintf("%s/api/v1/workflows/%s/executions/%s/abort", baseUrl, workflowExecution.Workflow.ID, workflowExecution.ExecutionId) + path := fmt.Sprintf("?reason=%s", url.QueryEscape(reason)) + if len(nodeId) > 0 { + path += fmt.Sprintf("&node=%s", url.QueryEscape(nodeId)) } - if len(httpsProxy) > 0 { - log.Printf("[INFO] Running with HTTPS proxy %s (env: HTTPS_PROXY)", httpsProxy) + if len(environment) > 0 { + path += fmt.Sprintf("&env=%s", url.QueryEscape(environment)) } - } - log.Printf("[INFO] All App Logs: %#v", allLogs) - _, err = client.Do(req) - if err != nil { - log.Printf("[WARNING] Failed abort request: %s", err) + //fmt.Println(url.QueryEscape(query)) + abortUrl += path + log.Printf("[INFO] Abort URL: %s", abortUrl) + + req, err := http.NewRequest( + "GET", + abortUrl, + nil, + ) + + if err != nil { + log.Println("[INFO] Failed building request: %s", err) + } + + // FIXME: Add an API call to the backend + authorization := os.Getenv("AUTHORIZATION") + if len(authorization) > 0 { + req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", authorization)) + } else { + log.Printf("[ERROR] No authorization specified for abort") + } + + req.Header.Add("Content-Type", "application/json") + client := &http.Client{ + Transport: &http.Transport{ + Proxy: nil, + }, + } + + httpProxy := os.Getenv("HTTP_PROXY") + httpsProxy := os.Getenv("HTTPS_PROXY") + if (len(httpProxy) > 0 || len(httpsProxy) > 0) && baseUrl != "http://shuffle-backend:5001" { + client = &http.Client{} + } else { + if len(httpProxy) > 0 { + log.Printf("[INFO] Running with HTTP proxy %s (env: HTTP_PROXY)", httpProxy) + } + if len(httpsProxy) > 0 { + log.Printf("[INFO] Running with HTTPS proxy %s (env: HTTPS_PROXY)", httpsProxy) + } + } + + log.Printf("[INFO] All App Logs: %#v", allLogs) + _, err = client.Do(req) + if err != nil { + log.Printf("[WARNING] Failed abort request: %s", err) + } + } else { + log.Printf("[INFO] NOT running abort during shutdown.") } log.Printf("[INFO] Finished shutdown (after %d seconds). ", sleepDuration) @@ -214,15 +186,8 @@ func deployApp(cli *dockerclient.Client, image string, identifier string, env [] Type: "json-file", Config: map[string]string{}, }, - Resources: container.Resources{}, - } - - // form container id and use it as network source if it's not empty - containerId = getThisContainerId() - if containerId != "" { - hostConfig.NetworkMode = container.NetworkMode(fmt.Sprintf("container:%s", containerId)) - } else { - log.Printf("[WARNING] Empty self container id, continue without NetworkMode") + Resources: container.Resources{}, + NetworkMode: container.NetworkMode(fmt.Sprintf("container:worker-%s", workflowExecution.ExecutionId)), } // Removing because log extraction should happen first @@ -760,6 +725,16 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) { Value: workflowExecution.ExecutionId, }) + action.Parameters = append(action.Parameters, shuffle.WorkflowAppActionParameter{ + Name: "source_node", + Value: trigger.ID, + }) + + action.Parameters = append(action.Parameters, shuffle.WorkflowAppActionParameter{ + Name: "source_auth", + Value: workflowExecution.Authorization, + }) + //trigger.LargeImage = "" //err = handleSubworkflowExecution(client, workflowExecution, trigger, action) //if err != nil { @@ -957,6 +932,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) { fmt.Sprintf("AUTHORIZATION=%s", workflowExecution.Authorization), fmt.Sprintf("CALLBACK_URL=%s", baseUrl), fmt.Sprintf("BASE_URL=%s", appCallbackUrl), + fmt.Sprintf("TZ=%s", timezone), } if strings.ToLower(os.Getenv("SHUFFLE_PASS_APP_PROXY")) == "true" { @@ -968,13 +944,19 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) { // Fixes issue: // standard_init_linux.go:185: exec user process caused "argument list too long" // https://devblogs.microsoft.com/oldnewthing/20100203-00/?p=15083 - maxSize := 32700 - len(string(actionData)) - 2000 - if len(executionData) < maxSize { - log.Printf("[INFO] ADDING FULL_EXECUTION because size is smaller than %d", maxSize) - env = append(env, fmt.Sprintf("FULL_EXECUTION=%s", string(executionData))) - } else { - log.Printf("[WARNING] Skipping FULL_EXECUTION because size is larger than %d", maxSize) - } + + // FIXME: Ensure to NEVER do this anymore + // This potentially breaks too much stuff. Better to have the app poll the data. + _ = executionData + /* + maxSize := 32700 - len(string(actionData)) - 2000 + if len(executionData) < maxSize { + log.Printf("[INFO] ADDING FULL_EXECUTION because size is smaller than %d", maxSize) + env = append(env, fmt.Sprintf("FULL_EXECUTION=%s", string(executionData))) + } else { + log.Printf("[WARNING] Skipping FULL_EXECUTION because size is larger than %d", maxSize) + } + */ // Uses a few ways of getting / checking if an app is available // 1. Try original with lowercase @@ -982,8 +964,8 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) { // 3. Add remote repo location images := []string{ image, - fmt.Sprintf("%s:%s_%s", baseimagename, strings.Replace(action.AppName, " ", "-", -1), action.AppVersion), fmt.Sprintf("%s/%s:%s_%s", registryName, baseimagename, parsedAppname, action.AppVersion), + fmt.Sprintf("%s:%s_%s", baseimagename, strings.Replace(action.AppName, " ", "-", -1), action.AppVersion), } // If cleanup is set, it should run for efficiency @@ -1131,7 +1113,7 @@ func handleExecutionResult(workflowExecution shuffle.WorkflowExecution) { if strings.Contains(buildBuf.String(), "errorDetail") { log.Printf("[ERROR] Docker build:\n%s\nERROR ABOVE: Trying to pull tags from: %s", buildBuf.String(), image) log.Printf("[DEBUG] Shutting down (14)") - shutdown(workflowExecution, action.ID, fmt.Sprintf("%s", err.Error()), true) + shutdown(workflowExecution, action.ID, fmt.Sprintf("Error deploying container: %s", buildBuf.String()), true) } log.Printf("[INFO] Successfully downloaded %s", image) @@ -1958,7 +1940,6 @@ func downloadDockerImageBackend(client *http.Client, imageName string) error { req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", authorization)) } else { log.Printf("[WARNING] No auth found - running backend download without it.") - //req.Header.Add("Authorization", fmt.Sprintf("Bearer db0373c6-1083-4dec-a05d-3ba73f02ccd4")) //return } @@ -2042,6 +2023,12 @@ func main() { } } + if timezone == "" { + timezone = "Europe/Amsterdam" + } + + log.Printf("[INFO] Running with timezone %s", timezone) + //imageName := fmt.Sprintf("%s/%s:shuffle_openapi_1.0.0", registryName, baseimagename) //downloadDockerImageBackend(client, imageName)