Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: update target counter example to use GA APIs #49

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ This is a list of examples and scripts compiled by the Snyk Customer Experience


# Scripts
- [How to build a collection of tagged projects](project-collections)
- [How to extract all issues pertinent to a tagged project](tagged-project-issues)
- ["Remediation Advice" for Snyk Container](scripts/Remediation-container.md)
- [Dependencies by project CSV](scripts/dependencies-csv.md)
- [Fixed Issues by Org CSV](scripts/fixed-issues-by-org.md)
Expand Down
3 changes: 3 additions & 0 deletions bulk-delete/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ PROJECTS ARE DE-ACTIVATED BY DEFAULT AND NO ACTIONS ARE APPLIED UNLESS --FORCE F
--help : Returns this page \n--orgs/<br>
--orgs : A set of orgs upon which to perform delete, be sure to use org slug instead of org display name (use ! for all orgs)<br>
--sca-types : Defines SCA type/s of projects to deletes <br>
--after : Only delete projects that were created after a certain date time (in ISO 8601 format, i.e 2023-09-01T00:00:00.000Z) <br>
--before : Only delete projects that were created before a certain date time (in ISO 8601 format, i.e 2023-09-01T00:00:00.000Z)<br>
--ignore-keys : An array of key's, if any of these key's are present in a project name then that project will not be targeted for deletion/deactivation<br>
--products : Defines product/s types of projects to delete(opensource,container,iac,or sast)<br>
--delete : By default this script will deactivate projects, add this flag to delete active projects instead<br>
--delete-non-active-projects : By default this script will deactivate projects, add this flag to delete non-active projects instead (if this flag is active no active projects will be deleted)<br>
Expand Down
2 changes: 1 addition & 1 deletion bulk-delete/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pysnyk
pysnyk==0.9.8
yaspin
64 changes: 57 additions & 7 deletions bulk-delete/snyk-bulk-delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
from yaspin import yaspin
from helperFunctions import *
import time
from datetime import datetime

helpString ='''--help : Returns this page \n--force : By default this script will perform a dry run, add this flag to actually apply changes\n--delete : By default this script will deactivate projects, add this flag to delete active projects instead \n--delete-non-active-projects : By default this script will deactivate projects, add this flag to delete non-active projects instead (if this flag is present only non-active projects will be deleted) \n--origins : Defines origin types of projects to delete\n--orgs : A set of orgs upon which to perform delete,be sure to use org slug instead of org display name (use ! for all orgs)\n--scatypes : Defines SCA type/s of projects to deletes \n--products : Defines product/s types of projects to delete\n--delete-empty-orgs : This will delete all orgs that do not have any projects in them \n* Please replace spaces with dashes(-) when entering orgs \n* If entering multiple values use the following format: "value-1 value-2 value-3"


helpString ='''--help : Returns this page \n--force : By default this script will perform a dry run, add this flag to actually apply changes\n--delete : By default this script will deactivate projects, add this flag to delete active projects instead \n--delete-non-active-projects : By default this script will deactivate projects, add this flag to delete non-active projects instead (if this flag is present only non-active projects will be deleted) \n--origins : Defines origin types of projects to delete\n--orgs : A set of orgs upon which to perform delete,be sure to use org slug instead of org display name (use ! for all orgs)\n--scatypes : Defines SCA type/s of projects to deletes \n--products : Defines product/s types of projects to delete\n--delete-empty-orgs : This will delete all orgs that do not have any projects in them \n* Please replace spaces with dashes(-) when entering orgs \n* If entering multiple values use the following format: "value-1 value-2 value-3" \n--after : Only delete projects that were created after a certain date time (in ISO 8601 format, i.e 2023-09-01T00:00:00.000Z)\n--after : Only delete projects that were created before a certain date time (in ISO 8601 format, i.e 2023-09-01T00:00:00.000Z)\n--ignore-keys : An array of key's, if any of these key's are present in a project name then that project will not be targeted for deletion/deactivation
'''

#get all user orgs and verify snyk API token
Expand All @@ -14,6 +17,32 @@
except snyk.errors.SnykHTTPError as err:
print("💥 Ran into an error while fetching account details, please check your API token")
print( helpString)
def is_date_between(curr_date_str, before_date_str, after_date_str):
# Parse the current date string into a datetime object
curr_date = datetime.strptime(curr_date_str, '%Y-%m-%dT%H:%M:%S.%fZ')

# Parse the before date string into a datetime object if it's not empty
if before_date_str:
before_date = datetime.strptime(before_date_str, '%Y-%m-%dT%H:%M:%S.%fZ')
else:
before_date = None

# Parse the after date string into a datetime object if it's not empty
if after_date_str:
after_date = datetime.strptime(after_date_str, '%Y-%m-%dT%H:%M:%S.%fZ')
else:
after_date = None

# Check if the current date is between the before and after dates
if before_date and after_date:
return curr_date <= before_date and curr_date >= after_date
elif before_date:
return curr_date <= before_date
elif after_date:
return curr_date >= after_date
else:
# If both before and after dates are empty, return True
return True

def main(argv):
inputOrgs = []
Expand All @@ -24,11 +53,14 @@ def main(argv):
dryrun = True
deactivate = True
deleteNonActive = False
beforeDate = ""
afterDate = ""
ignoreKeys = []


#valid input arguments declared here
try:
opts, args = getopt.getopt(argv, "hofd",["help", "orgs=", "sca-types=", "products=", "origins=", "force", "delete-empty-orgs", "delete", "delete-non-active-projects"] )
opts, args = getopt.getopt(argv, "hofd",["help", "orgs=", "sca-types=", "products=", "origins=", "ignore-keys=", "before=","after=", "force", "delete-empty-orgs", "delete", "delete-non-active-projects"] )
except getopt.GetoptError:
print("Error parsing input, please check your syntax")
sys.exit(2)
Expand All @@ -46,7 +78,6 @@ def main(argv):
if opt == '--sca-types':
scaTypes = [scaType.lower() for scaType in arg.split()]
if opt == '--products':
print(arg)
products =[product.lower() for product in arg.split()]
if opt == '--origins':
origins =[origin.lower() for origin in arg.split()]
Expand All @@ -59,6 +90,12 @@ def main(argv):
if opt =='--delete-non-active-projects':
deactivate = False
deleteNonActive = True
if opt =='--before':
beforeDate = arg
if opt =='--after':
afterDate = arg
if opt =='--ignore-keys':
ignoreKeys =[key.lower() for key in arg.split()]
#error handling if no filters declared
filtersEmpty = len(scaTypes) == 0 and len(products) == 0 and len(origins) == 0
if filtersEmpty and not deleteorgs:
Expand All @@ -74,7 +111,7 @@ def main(argv):

#print dryrun message
if dryrun:
print("\033[93mTHIS IS A DRY RUN NOTHING, WILL BE DELETED! USE --FORCE TO APPLY ACTIONS\u001b[0m")
print("\033[93mTHIS IS A DRY RUN, NOTHING WILL BE DELETED! USE --FORCE TO APPLY ACTIONS\u001b[0m")

#delete functionality
for currOrg in userOrgs:
Expand All @@ -89,13 +126,26 @@ def main(argv):
#cycle through all projects in current org and delete projects that match filter
for currProject in currOrg.projects.all():


#variables which determine whether project matches criteria to delete, if criteria is empty they will be defined as true
scaTypeMatch = False
originMatch = False
productMatch = False
dateMatch = False
nameMatch = True
isActive = currProject.isMonitored

#dateMatch validation
try:
dateMatch = is_date_between(currProject.created, beforeDate, afterDate)
except:
print("error processing before/after datetimes, please check your format")
sys.exit(2)
#nameMatch validation
for key in ignoreKeys:
if key in currProject.name:
nameMatch = False

#if scatypes are not declared or curr project type matches filter criteria then return true
if len(scaTypes) != 0:
if currProject.type in scaTypes:
Expand All @@ -119,7 +169,7 @@ def main(argv):
productMatch = True

#delete active project if filter are meet
if scaTypeMatch and originMatch and productMatch and isActive and not filtersEmpty and not deleteNonActive:
if scaTypeMatch and originMatch and productMatch and isActive and not filtersEmpty and not deleteNonActive and dateMatch and nameMatch:
currProjectDetails = f"Origin: {currProject.origin}, Type: {currProject.type}, Product: {currProjectProductType}"
action = "Deactivating" if deactivate else "Deleting"
spinner = yaspin(text=f"{action}\033[1;32m {currProject.name}", color="yellow")
Expand All @@ -135,7 +185,7 @@ def main(argv):
except exception as e:
spinner.fail("💥 ")
#delete non-active project if filters are meet
if scaTypeMatch and originMatch and productMatch and (not isActive) and deleteNonActive and not filtersEmpty:
if scaTypeMatch and originMatch and productMatch and (not isActive) and deleteNonActive and not filtersEmpty and dateMatch and nameMatch:
currProjectDetails = f"Origin: {currProject.origin}, Type: {currProject.type}, Product: {currProjectProductType}"
spinner = yaspin(text=f"Deleting\033[1;32m {currProject.name}", color="yellow")
spinner.write(f"\u001b[0m Processing project: \u001b[34m{currProjectDetails}\u001b[0m, Status Below👇")
Expand Down
61 changes: 61 additions & 0 deletions project-collections/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Collections
## Build Collection
A script that creates a collection and/or adds tagged projects to it. The collection is created if it does not exist.
If the collection exists already, the script continues to add projects with the specified tag to it. If the projects
exist within the collection, the script continues to add the next project to the collection.

## Use Case
Collections allow snyk projects within a given organisation to be viewed through a single pane of glass. Projects may
be tagged in order to provide a key that identifies them as a collective. These tagged projects are added to the
collection.

## Call predicate
build_collection -a \<snyk-auth-token\> -v "snyk-rest-api-version" -g "snyk-group-name" -o "snyk-org-name" -c "collection-name" -t "tag-name:tag-value"

## Example usage:
#### Full argument names
build_collection --snyk_token \<snyk-auth-token\>
--grp_name "kevin.matthews Group" --org_name "PR Test Org" --collection_name="squad-3" --project_tags "squad-name:squad-3" --api_ver "2024-01-23"
##### Assuming default arg values where possible
build_collection --snyk_token \<snyk-auth-token\>
--grp_name "kevin.matthews Group" --org_name "PR Test Org" --collection_name="squad-3" --project_tags "squad-name:squad-3"


## Remove Collection
A script that deletes a named collection. This is the brut force full delete of the collection. This script does not
delete a sub-set of the projects within the collection.

## Use Case
A collection abstracts a view of assigned projects. You need to delete the collection.

## Call predicate
remove_collection -a \<snyk-auth-token\> -v "snyk-rest-api-version" -g "snyk-group-name" -o "snyk-org-name" -c "squad-3"

## Example usage:
#### Full argument names
remove_collection --snyk_token \<snyk-auth-token\>
--grp_name "kevin.matthews Group" --org_name "PR Test Org" --collection_name="squad-3" --api_ver "2024-01-23"
##### Assuming default arg values where possible
remove_collection --snyk_token \<snyk-auth-token\>
--grp_name "kevin.matthews Group" --org_name "PR Test Org" --collection_name="squad-3"



#### Argument flags
remove_collection -a \<snyk-auth-token\> -g "kevin.matthews Group" -o "PR Test Org" -c "squad-3" -v "2024-01-23"
##### Assuming default arg values where possible
remove_collection -a \<snyk-auth-token\> -g "kevin.matthews Group" -o "PR Test Org" -c "squad-3"

### Arguments
- --snyk_token <snyk_auth_token>
- --grp_name "Snyk group name in which tagged projects are to be parsed"
- --org_name "Snyk org name in which tagged projects are to be parsed"
- --collection_name "The name of the collection to be created and to which tagged projects will be added."
- --project_tags "Comma delimited list of 'tags', all of which must be assigned to a project for it to be identified"
- --effective_severity_level "Comma separated list of severities to be identified in tagged project issues payload"
- --api_ver "The version of the Snyk API to be used (default recommended)"

### Note
Some of the APIs used are in beta. Others are GA. As the beta's become GA, I will remove the 'hard-coded' use of their
beta counterparts. Meantime, please DO NOT specify a beta version of an API should you wish to choose a specific
version. It is recommended at this time that you allow the default version to be used.
76 changes: 76 additions & 0 deletions project-collections/build_collection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import argparse
import json
import os
import urllib

import utils.util_func


def get_arguments():
parser = argparse.ArgumentParser(description='This script enables you to configure IQ Server from JSON\
data, thus supporting the config-as-code requirement of Sonatype customers')
parser.add_argument('-a', '--snyk_token', required=True)
parser.add_argument('-g', '--grp_name', required=True)
parser.add_argument('-o', '--org_name', required=True)
parser.add_argument('-c', '--collection_name', required=True)
parser.add_argument('-t', '--project_tags', required=True)
parser.add_argument('-v', '--api_ver', default="2024-01-23")

args = vars(parser.parse_args())
return args


def next_page(response):
# response['links']['next']
try:
pagination = urllib.parse.parse_qs(response['links']['next'],
keep_blank_values=False, strict_parsing=False,
encoding='utf-8', errors='replace',
max_num_fields=None, separator='&')
pagination = pagination['starting_after'][0]
except:
pagination = None
return pagination


def add_proj_to_collection(headers, args, org, collection_id):
op_pagination = None
op_response = None
try:
while True:
# Use of the project_tags ensures only those with the right tag are returned
op_response = json.loads(
utils.rest_api.org_projects(headers, args["api_ver"], org,
args["project_tags"], op_pagination))

for project in op_response['data']:
# iterate over the tags in each project and persist it i it has one of the tags
# of interest
utils.rest_api.add_project_to_collection(headers, args, org, collection_id, project)

# Next page?
op_pagination = next_page(op_response)
if op_pagination is None:
break
except Exception:
print("POST call to /collections - Unable to build collection")
print(json.dumps(op_response, indent=4))
return




# Press the green button in the gutter to run the script.
if __name__ == '__main__':

args = get_arguments()
os.environ['SNYK_TOKEN'] = args['snyk_token']

headers = {
'Content-Type': 'application/vnd.api+json',
'Authorization': 'token {0}'.format(os.getenv('SNYK_TOKEN'))
}

utils.util_func.process_collection(headers, args, add_proj_to_collection)


38 changes: 38 additions & 0 deletions project-collections/remove_collection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import argparse
import os
import utils.util_func


def get_arguments():
parser = argparse.ArgumentParser(description='This script enables you to configure IQ Server from JSON\
data, thus supporting the config-as-code requirement of Sonatype customers')
parser.add_argument('-a', '--snyk_token', required=True)
parser.add_argument('-g', '--grp_name', required=True)
parser.add_argument('-o', '--org_name', required=True)
parser.add_argument('-c', '--collection_name', required=True)
parser.add_argument('-v', '--api_ver', default="2024-01-23")

args = vars(parser.parse_args())
return args


def remove_collection(headers, args, org, collection_id):
utils.rest_api.remove_collection(headers, args, org, collection_id)




# Press the green button in the gutter to run the script.
if __name__ == '__main__':

args = get_arguments()
os.environ['SNYK_TOKEN'] = args['snyk_token']

headers = {
'Content-Type': 'application/vnd.api+json',
'Authorization': 'token {0}'.format(os.getenv('SNYK_TOKEN'))
}

utils.util_func.process_collection(headers, args, remove_collection)


Empty file.
Loading