Skip to content

Commit

Permalink
RuntimeValidator - 16777 - Added SDK version validation (#185)
Browse files Browse the repository at this point in the history
  • Loading branch information
igorski-r7 authored Apr 24, 2024
1 parent c6478f6 commit 1e683b3
Show file tree
Hide file tree
Showing 32 changed files with 896 additions and 14 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ be on your way to contributing!

## Changelog

* 2.47.18 - `HelpInputOutputValidator` | `SpecPropertiesValidator` - Update to enable `placeholder` and `tooltip` validation
* 2.47.18 - `HelpInputOutputValidator` | `SpecPropertiesValidator` - Update to enable `placeholder` and `tooltip` validation | `RuntimeValidator` - Added SDK version validation
* 2.47.17 - `SpecPropertiesValidator` - Added new excludeProduct field validator
* 2.47.16 - `HelpInputOutputValidator` - Update error message from `icon-plugin` to `insight-plugin` | `DockerValidator` - Print full error message and change instances of `icon-plugin` to `insight-plugin`
* 2.47.15 - `TitleValidator` - Change validator to print all issues rather than break on the first
Expand Down
1 change: 1 addition & 0 deletions icon_validator/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DEFAULT_TIMEOUT = 3
70 changes: 57 additions & 13 deletions icon_validator/rules/plugin_validators/runtime_validator.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import os
import glob
import os
import re
from typing import List, Union

from icon_validator.rules.validator import KomandPluginValidator
import requests
from icon_validator.constants import DEFAULT_TIMEOUT
from icon_validator.exceptions import ValidationException
from icon_validator.rules.validator import KomandPluginValidator


class RuntimeValidator(KomandPluginValidator):
Expand Down Expand Up @@ -45,31 +49,71 @@ def validate_caching(spec):
for path in paths:
for root, dirs, files in os.walk(path):
for file in files:
with open(os.path.join(root, file), 'r') as open_file:
with open(os.path.join(root, file), "r") as open_file:
file_str = open_file.read().replace("\n", "")

if "cache" in file_str:
raise ValidationException(f"Cloud ready plugins cannot contain caching. "
f"Update {str(os.path.join(root, file))}.")
raise ValidationException(
f"Cloud ready plugins cannot contain caching. "
f"Update {str(os.path.join(root, file))}."
)

@staticmethod
def validate_dockerfile(spec, latest_images):
def validate_dockerfile(self, spec, latest_images):
if "setup.py" in os.listdir(spec.directory):
with open(f"{spec.directory}/setup.py", "r") as setup_file:
setup_str = setup_file.read().replace("\n", "")

if "insightconnect-plugin-runtime" in setup_str:
with open(f"{spec.directory}/Dockerfile", "r") as docker_file:
docker_str = docker_file.read().replace("\n", "")
docker_str = docker_file.read()
docker_image = next(
filter(lambda image: image in docker_str, latest_images),
None,
)

if not any(image in docker_str for image in latest_images):
raise ValidationException("insightconnect-plugin-runtime is being used in setup.py. "
"Update Dockerfile accordingly to use latest base image.")
raise ValidationException(
"insightconnect-plugin-runtime is being used in setup.py. "
"Update Dockerfile accordingly to use latest base image."
)

current_tag = self._parse_image_tag(docker_image, docker_str)
latest_tags = self._get_latest_runtime_tags(docker_image)
if current_tag and current_tag not in latest_tags:
raise ValidationException(
f"The current base image tag ({current_tag}) set is not latest. "
f"Please update Dockerfile accordingly to use latest base image."
f" Current latest tags are: {latest_tags}" if latest_tags else ""
)

@staticmethod
def _parse_image_tag(image_name: str, dockerfile_content: str) -> Union[str, None]:
match = re.search(f"{image_name}:\s*(.*)", dockerfile_content)
if match:
return match.group(1)
return None

@staticmethod
def _get_latest_runtime_tags(image_name: str) -> List[str]:
latest_tags = ["latest"]
try:
response = requests.request(
"GET",
f"https://hub.docker.com/v2/repositories/{image_name}/tags",
params={"page_size": 3, "ordering": "last_updated"},
timeout=DEFAULT_TIMEOUT,
)
response.raise_for_status()
return latest_tags + [tag.get("name") for tag in response.json().get("results", [])]
except Exception:
return latest_tags

def validate(self, spec):
latest_images = ["rapid7/insightconnect-python-3-plugin",
"rapid7/insightconnect-python-3-slim-plugin"]
RuntimeValidator.validate_dockerfile(spec, latest_images)
latest_images = [
"rapid7/insightconnect-python-3-plugin",
"rapid7/insightconnect-python-3-slim-plugin",
]
self.validate_dockerfile(spec, latest_images)

with open(f"{spec.directory}/Dockerfile", "r") as file:
docker_str = file.read().replace("\n", "")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"spec": "7b971f1ccf8214be1b140fe212ade68d",
"manifest": "c4fe1b5757c0fbf72a28679cc6303e30",
"setup": "9d01465bd990dde0f93e26a9fbda8a33",
"schemas": [
{
"identifier": "decode/schema.py",
"hash": "e8816b23112cbb301c9255c29323c4a8"
},
{
"identifier": "encode/schema.py",
"hash": "70afbd79bd72035de62b32983ee57ba3"
},
{
"identifier": "connection/schema.py",
"hash": "da5382221ca2a33a2f854e17b068d502"
},
{
"identifier": "test_task/schema.py",
"hash": "930a8c07873126927dbcaf6d8e5226fa"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
unit_test/**/*
unit_test
examples/**/*
examples
tests
tests/**/*
**/*.json
**/*.tar
**/*.gz
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
FROM rapid7/insightconnect-python-3-plugin:4
LABEL organization=komand
LABEL sdk=python
LABEL type=plugin

ENV SSL_CERT_FILE /etc/ssl/certs/ca-certificates.crt
ENV SSL_CERT_DIR /etc/ssl/certs
ENV REQUESTS_CA_BUNDLE /etc/ssl/certs/ca-certificates.crt

ADD ./plugin.spec.yaml /plugin.spec.yaml
ADD . /python/src

WORKDIR /python/src
# Add any package dependencies here

# End package dependencies
RUN if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
RUN python setup.py build && python setup.py install


ENTRYPOINT ["/usr/local/bin/komand_base64"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Include other Makefiles for improved functionality
INCLUDE_DIR = ../../tools/Makefiles
MAKEFILES := $(wildcard $(INCLUDE_DIR)/*.mk)
# We can't guarantee customers will have the include files
# - prefix to ignore Makefiles when not present
# https://www.gnu.org/software/make/manual/html_node/Include.html
-include $(MAKEFILES)

ifneq ($(MAKEFILES),)
$(info [$(YELLOW)*$(NORMAL)] Use ``make menu`` for available targets)
$(info [$(YELLOW)*$(NORMAL)] Including available Makefiles: $(MAKEFILES))
$(info --)
else
$(warning Makefile includes directory not present: $(INCLUDE_DIR))
endif

VERSION?=$(shell grep '^version: ' plugin.spec.yaml | sed 's/version: //')
NAME?=$(shell grep '^name: ' plugin.spec.yaml | sed 's/name: //')
VENDOR?=$(shell grep '^vendor: ' plugin.spec.yaml | sed 's/vendor: //')
CWD?=$(shell basename $(PWD))
_NAME?=$(shell echo $(NAME) | awk '{ print toupper(substr($$0,1,1)) tolower(substr($$0,2)) }')
PKG=$(VENDOR)-$(NAME)-$(VERSION).tar.gz

# Set default target explicitly. Make's default behavior is the first target in the Makefile.
# We don't want that behavior due to includes which are read first
.DEFAULT_GOAL := default # Make >= v3.80 (make -version)


default: image tarball

tarball:
$(info [$(YELLOW)*$(NORMAL)] Creating plugin tarball)
rm -rf build
rm -rf $(PKG)
tar -cvzf $(PKG) --exclude=$(PKG) --exclude=tests --exclude=run.sh *

image:
$(info [$(YELLOW)*$(NORMAL)] Building plugin image)
docker build --pull -t $(VENDOR)/$(NAME):$(VERSION) .
docker tag $(VENDOR)/$(NAME):$(VERSION) $(VENDOR)/$(NAME):latest

regenerate:
$(info [$(YELLOW)*$(NORMAL)] Regenerating schema from plugin.spec.yaml)
icon-plugin generate python --regenerate

export: image
$(info [$(YELLOW)*$(NORMAL)] Exporting docker image)
@printf "\n ---> Exporting Docker image to ./$(VENDOR)_$(NAME)_$(VERSION).tar\n"
@docker save $(VENDOR)/$(NAME):$(VERSION) | gzip > $(VENDOR)_$(NAME)_$(VERSION).tar

# Make will not run a target if a file of the same name exists unless setting phony targets
# https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html
.PHONY: default tarball image regenerate
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env python
# GENERATED BY KOMAND SDK - DO NOT EDIT
import os
import json
from sys import argv

Name = "Base64"
Vendor = "rapid7"
Version = "1.1.2"
Description = "Encode and decode data using the base64 alphabet"


def main():
if 'http' in argv:
if os.environ.get("GUNICORN_CONFIG_FILE"):
with open(os.environ.get("GUNICORN_CONFIG_FILE")) as gf:
gunicorn_cfg = json.load(gf)
if gunicorn_cfg.get("worker_class", "sync") == "gevent":
from gevent import monkey
monkey.patch_all()
elif 'gevent' in argv:
from gevent import monkey
monkey.patch_all()

import insightconnect_plugin_runtime
from komand_base64 import connection, actions, triggers, tasks

class ICONBase64(insightconnect_plugin_runtime.Plugin):
def __init__(self):
super(self.__class__, self).__init__(
name=Name,
vendor=Vendor,
version=Version,
description=Description,
connection=connection.Connection()
)
self.add_action(actions.Decode())

self.add_action(actions.Encode())

self.add_task(tasks.TestTask())


"""Run plugin"""
cli = insightconnect_plugin_runtime.CLI(ICONBase64())
cli.run()


if __name__ == "__main__":
main()
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 1e683b3

Please sign in to comment.