mirror of
https://github.com/Azure/k8s-deploy.git
synced 2026-04-03 10:52:16 +08:00
* Add missing API switch for GHES (#200) * Vidya reddy/prettier code (#203) * switch none deployment strategy to basic (#204) * switch none deployment strategy to basic * update readme * update deployment strategy fallthrough logic * comment fixed * add disclaimer for basic strategy only supporting deploy action * Hari/beautify logs (#206) * Logging changes for deploy * Logging Changes with group * format check changes * Add ncc build to build script (#208) Co-authored-by: Vidya Reddy <vidyareddy@microsoft.com> * Logging Changes for Promote, Reject actions (#207) * add clean function (#211) * Added Traffic split annotations (#215) * Added Traffic split annotations * traffic split - blueGreen deployment * traffic split - canary deployment * Traffic split annotations - canary deployment * updated Readme and action.yml * Traffic split - canary deployment * clean code * Clean code * Clean code * Create annotation object * Updated Readme and action.yml * Spelling correction Co-authored-by: Vidya Reddy <vidyareddy@microsoft.com> * Swap annotation key to actions.github.com prefix (#216) * Private Cluster functionality (#214) * Fixed Blue/Green Strategy Ingress Route-Method Glitch (#217) * Added some tests, not sure what else to try but gonna think of more examples * forgot some files * reverted package-lock.json * Added empty dir test * Cleaned up some extra spaces * Add node modules and compiled JavaScript from main * forgot to actually include functionality * removed unnecessary files * Update .gitignore * Update .gitignore * Update .gitignore * thx david * renamed searchFilesRec * integrations test fix * added examples to README * added note about depth * added additional note * removed ticks * changed version string * removed conflict on readme * Added tests for bluegreen helper and resolved issue with ingress not being read correctly, still have to figure out why new services aren't showing up * resolved services name issue * looks functional, beginning refactor now * refactored deploy methods for type error * Removed refactor comments * prettier * implemented Oliver's feedback * prettier * added optional chaining operator * removed refactor comment Co-authored-by: Jaiveer Katariya <jaiveerkatariya@Jaiveers-MacBook-Pro.local> Co-authored-by: Oliver King <oking3@uncc.edu> Co-authored-by: Jaiveer Katariya <jaiveerkatariya@Jaiveers-MBP.lan> * Blue/Green Refactor (#229) * fresh new branch * Added coverage to gitignore Signed-off-by: Jaiveer Katariya <jaiveerkatariya@Jaiveers-MBP.lan> * reverted package-lock.json Signed-off-by: Jaiveer Katariya <jaiveerkatariya@Jaiveers-MBP.lan> Co-authored-by: Jaiveer Katariya <jaiveerkatariya@Jaiveers-MBP.lan> * consider slashes while cleaning labels (#231) fix prettier format check errors * Fix README.md typo (#235) * Bump @actions/core from 1.9.0 to 1.9.1 (#233) Bumps [@actions/core](https://github.com/actions/toolkit/tree/HEAD/packages/core) from 1.9.0 to 1.9.1. - [Release notes](https://github.com/actions/toolkit/releases) - [Changelog](https://github.com/actions/toolkit/blob/main/packages/core/RELEASES.md) - [Commits](https://github.com/actions/toolkit/commits/HEAD/packages/core) --- updated-dependencies: - dependency-name: "@actions/core" dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Add permissions to README.md (#236) * Add permissions to README.md * remove space * prettier * remove extra changes * fix spacing * Add the bug report and feature request form (#237) * Added the bug report and feature request form * updated the url * Fix issue form (#238) * Fix description about baseline-and-canary-replicas (#241) * Resolved issue with Canary deploy (#247) * Added support message (#249) * Deploy with Manifests from URLs (#251) * added functionality, need to add/modify existing tests * added tests * updated readme * prettier * Fix private cluster kubectl exit code bug (#252) * add private cluster exitCode check * add proper output * Added Integration Tests, Resolved Bugs With Annotations (#255) * First commit - made manifests for test deployments, made manifests for i tests for other deployment strategies * broke down blue/green * added latest tags to test manifests for new tags * remade tester * ready to test bgi * using all but first index of argv * careless error with dicts * added test to namespace * realized i was silencing error * indexing containers * keyerror * logging bc python errors are weird * expected still string * parsed args behaving weirdly * test seems to be working now, applying changes to other YAMLs now * blue/green ready to test * oops * oops * Added additional labels to check * hyphen * Added our annotations * lol * added our labels to services too * nonetype issue' * nonetype issue' * narrowing down parameter * fixed annotations issue with promote * adding debhug statement to figure out why services aren't getting annotations * this should fix annotations issue for service * not sure why this wasn't caught by intellisense * should be fixed with removing comma but adding logs in case * added linkerd install * verification * upgraded kubernetes version * removing crds * proxy option * Added smi extension * logging service * smi svcs also getting labeled now * matching ts type * not sure where stable service is going * remaining svc and deployment should match * keeping stable service and ts object * updated tests to reflect keeping ts object * no green svc after promote * duh * lol * canary work * canary test ready * logging for ing, filename for canary * changed ingress svc key and returning svc files from smi canary deployment * ts name * forgot about baseline in first deploy * * * * * smi canary should annotate, fixed cleanup * typescript issue plus percentage * forgot to type extra method * removed cleaned up objects from annotate list * logging because services aren't getting removed * moving to try/catch strategy of annotation since deletion can fail silently/with warnings * moved label to individual * removing canary service check after promote * pod ready for testing * set weights to 1000 * selectors * * * percentage * * * typing * mixed up pod and smi * fixed tests * prettier * forgot to remove canary * cleanup * Added oliver's feedback + more cleanup * ncc as dev dependency * npx * going back to global ncc install bc npm is being weird * prettier * removed unnecessary post step * new commit with all changes (#258) * Fixing Ubuntu Runner Issue (#259) * changed ubuntu runner * changed minikube action * Version formatting * nonedriveR * update kube version * installing conntrack' * updated other actions * update bg ingress api version * prettify * updated ingress backend for new api version * Added path type * prettify * Add skip tls flag (#260) * Add node modules and compiled JavaScript from main Signed-off-by: Jaiveer Katariya <jaiveerkatariya@Jaiveers-MBP.lan> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: nv35 <76777923+nv35@users.noreply.github.com> Co-authored-by: Vidya <59590642+Vidya2606@users.noreply.github.com> Co-authored-by: David Gamero <david340804@gmail.com> Co-authored-by: Hariharan Subramanian <105889062+hsubramanianaks@users.noreply.github.com> Co-authored-by: Vidya Reddy <vidyareddy@microsoft.com> Co-authored-by: Oliver King <oking3@uncc.edu> Co-authored-by: Marcus-Hines <marcus.chris.hines@gmail.com> Co-authored-by: Jaiveer Katariya <35347859+jaiveerk@users.noreply.github.com> Co-authored-by: Jaiveer Katariya <jaiveerkatariya@Jaiveers-MacBook-Pro.local> Co-authored-by: Jaiveer Katariya <jaiveerkatariya@Jaiveers-MBP.lan> Co-authored-by: Alexander Bartsch <alex@dashlabs.de> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Kenta Nakase <parroty@users.noreply.github.com> Co-authored-by: Asa Gayle <azmatch.gayle@gmail.com>
253 lines
8.6 KiB
Python
253 lines
8.6 KiB
Python
from operator import truediv
|
|
import os
|
|
import sys
|
|
import json
|
|
from unicodedata import name
|
|
|
|
# This integration test is used to confirm that k8s resources of a specified name, type, and configuration have been deployed.
|
|
# Expected configurations are fed into the python script as command-line arguments and are compared to the configuration of resources that have been deployed.
|
|
|
|
# args will be formatted like labels=testkey:testValue,otherKey=otherValue
|
|
# or for singular ones, just with containerName=container
|
|
|
|
|
|
kindKey = "kind"
|
|
nameKey = "name"
|
|
containerKey = "containerName"
|
|
labelsKey = "labels"
|
|
annotationsKey = "annotations"
|
|
selectorLabelsKey = "selectorLabels"
|
|
namespaceKey = "namespace"
|
|
ingressServicesKey = "ingressServices"
|
|
tsServicesKey = "tsServices"
|
|
privateKey = "private"
|
|
|
|
|
|
def parseArgs(sysArgs):
|
|
argsDict = stringListToDict(sysArgs, "=")
|
|
|
|
# mandatory parameters
|
|
if not kindKey in argsDict:
|
|
raise ValueError(f"missing key: {kindKey}")
|
|
|
|
if not nameKey in argsDict:
|
|
raise ValueError(f"missing key: {nameKey}")
|
|
|
|
if not namespaceKey in argsDict:
|
|
raise ValueError(f"missing key: {namespaceKey}")
|
|
|
|
# reformat map-like parameters (eg, paramName=key1:value1,key2:value2)
|
|
if labelsKey in argsDict:
|
|
argsDict[labelsKey] = stringListToDict(
|
|
argsDict[labelsKey].split(","), ":")
|
|
|
|
if annotationsKey in argsDict:
|
|
argsDict[annotationsKey] = stringListToDict(
|
|
argsDict[annotationsKey].split(","), ":")
|
|
|
|
if selectorLabelsKey in argsDict:
|
|
argsDict[selectorLabelsKey] = stringListToDict(
|
|
argsDict[selectorLabelsKey].split(","), ":")
|
|
|
|
if tsServicesKey in argsDict:
|
|
argsDict[tsServicesKey] = stringListToDict(
|
|
argsDict[tsServicesKey].split(","), ":")
|
|
|
|
for key in argsDict[tsServicesKey]:
|
|
argsDict[tsServicesKey][key] = int(argsDict[tsServicesKey][key])
|
|
|
|
# reformat list-like parameters (eg, paramName=value1,value2,value3)
|
|
if ingressServicesKey in argsDict:
|
|
argsDict[ingressServicesKey] = argsDict[ingressServicesKey].split(",")
|
|
|
|
return argsDict
|
|
|
|
|
|
def stringListToDict(args: list[str], separator: str):
|
|
parsedArgs = {}
|
|
for arg in args:
|
|
print(f"parsing arg {arg}")
|
|
argSplit = arg.split(separator)
|
|
parsedArgs[argSplit[0]] = argSplit[1]
|
|
|
|
return parsedArgs
|
|
|
|
|
|
def verifyDeployment(deployment, parsedArgs):
|
|
# test container image, labels, annotations, selector labels
|
|
if not containerKey in parsedArgs:
|
|
raise ValueError(
|
|
f"expected container image name not provided to inspect deployment {parsedArgs[nameKey]}")
|
|
|
|
actualImageName = deployment['spec']['template']['spec']['containers'][0]['image']
|
|
if not actualImageName == parsedArgs[containerKey]:
|
|
return False, f"expected container image name {parsedArgs[containerKey]} but got {actualImageName} instead"
|
|
|
|
if not selectorLabelsKey in parsedArgs:
|
|
raise ValueError(
|
|
f"expected selector labels not provided to inspect deployment {parsedArgs[nameKey]}")
|
|
dictMatch, msg = compareDicts(
|
|
deployment['spec']['selector']['matchLabels'], parsedArgs[selectorLabelsKey], selectorLabelsKey)
|
|
if not dictMatch:
|
|
return dictMatch, msg
|
|
|
|
if labelsKey in parsedArgs:
|
|
dictMatch, msg = compareDicts(
|
|
deployment['metadata']['labels'], parsedArgs[labelsKey], labelsKey)
|
|
if not dictMatch:
|
|
return dictMatch, msg
|
|
|
|
if annotationsKey in parsedArgs:
|
|
dictMatch, msg = compareDicts(
|
|
deployment['metadata']['annotations'], parsedArgs[annotationsKey], annotationsKey)
|
|
if not dictMatch:
|
|
return dictMatch, msg
|
|
|
|
return True, ""
|
|
|
|
|
|
def verifyService(service, parsedArgs):
|
|
# test selector labels, labels, annotations
|
|
if not selectorLabelsKey in parsedArgs:
|
|
raise ValueError(
|
|
f"expected selector labels not provided to inspect service {parsedArgs[nameKey]}")
|
|
dictMatch, msg = compareDicts(
|
|
service['spec']['selector'], parsedArgs[selectorLabelsKey], selectorLabelsKey)
|
|
if not dictMatch:
|
|
return dictMatch, msg
|
|
|
|
if labelsKey in parsedArgs:
|
|
print(f" service is {service}")
|
|
dictMatch, msg = compareDicts(
|
|
service['metadata']['labels'], parsedArgs[labelsKey], labelsKey)
|
|
if not dictMatch:
|
|
return dictMatch, msg
|
|
|
|
if annotationsKey in parsedArgs:
|
|
dictMatch, msg = compareDicts(
|
|
service['metadata']['annotations'], parsedArgs[annotationsKey], annotationsKey)
|
|
if not dictMatch:
|
|
return dictMatch, msg
|
|
|
|
return True, ""
|
|
|
|
|
|
def verifyIngress(ingress, parsedArgs):
|
|
# test services in paths
|
|
if not ingressServicesKey in parsedArgs:
|
|
raise ValueError(
|
|
f"expected services not provided to inspect ingress {parsedArgs[nameKey]}")
|
|
|
|
expectedIngresses = parsedArgs[ingressServicesKey]
|
|
for i in range(len(ingress['spec']['rules'][0]['http']['paths'])):
|
|
print(
|
|
f"service obj is {ingress['spec']['rules'][0]['http']['paths'][i]}")
|
|
svcName = ingress['spec']['rules'][0]['http']['paths'][i]['backend']['service']['name']
|
|
if svcName != expectedIngresses[i]:
|
|
return False, f"for ingress {parsedArgs[nameKey]} expected svc name {expectedIngresses[i]} at position {i} but got {svcName}"
|
|
|
|
return True, ""
|
|
|
|
|
|
def verifyTSObject(tsObj, parsedArgs):
|
|
if not tsServicesKey in parsedArgs:
|
|
raise ValueError(
|
|
f"expected services not provided to inspect ts object {parsedArgs[nameKey]}")
|
|
|
|
expectedServices = parsedArgs[tsServicesKey]
|
|
actualServices = {}
|
|
backends = tsObj['spec']['backends']
|
|
for i in range(len(backends)):
|
|
svcName = backends[i]['service']
|
|
svcWeight = int(backends[i]['weight'])
|
|
actualServices[svcName] = svcWeight
|
|
|
|
dictResult, msg = compareDicts(
|
|
actualServices, expectedServices, tsServicesKey)
|
|
if not dictResult:
|
|
return False, msg
|
|
|
|
return True, ""
|
|
|
|
|
|
def compareDicts(actual: dict, expected: dict, paramName=""):
|
|
actualKeys = actual.keys()
|
|
expectedKeys = expected.keys()
|
|
|
|
if not actualKeys == expectedKeys:
|
|
msg = f'dicts had different keys.\n actual: {actual}\n expected: {expected}'
|
|
if not paramName == "":
|
|
msg = f"for param {paramName}, " + msg
|
|
return False, msg
|
|
for key in actualKeys:
|
|
if not actual[key] == expected[key]:
|
|
msg = f'dicts differed at key {key}.\n actual[{key}] is {actual[key]} and expected[{key}] is {expected[key]}'
|
|
if not paramName == "":
|
|
msg = f"for param {paramName}, " + msg
|
|
return False, msg
|
|
|
|
return True, ""
|
|
|
|
|
|
def main():
|
|
parsedArgs: dict = parseArgs(sys.argv[1:])
|
|
RESULT = False
|
|
msg = "unknown type (no verification method currently exists)"
|
|
k8_object = None
|
|
|
|
kind = parsedArgs[kindKey]
|
|
name = parsedArgs[nameKey]
|
|
namespace = parsedArgs[namespaceKey]
|
|
cmd = 'kubectl get '+kind + ' '+name+' -n '+namespace+' -o json'
|
|
|
|
k8s_object = None
|
|
azPrefix = ""
|
|
try:
|
|
if privateKey in parsedArgs:
|
|
uniqueName = parsedArgs[privateKey]
|
|
azPrefix = f"az aks command invoke --resource-group {uniqueName} --name {uniqueName} --command "
|
|
cmd = azPrefix + "'" + cmd + "'"
|
|
outputString = os.popen(cmd).read()
|
|
successExit = "exitcode=0"
|
|
if successExit not in outputString:
|
|
raise ValueError(f"private cluster get failed for {kind} {name}")
|
|
|
|
objString = outputString.split(successExit)[1]
|
|
k8_object = json.loads(objString)
|
|
|
|
else:
|
|
k8_object = json.load(os.popen(cmd))
|
|
|
|
if k8_object == None:
|
|
raise ValueError(f"{kind} {name} was not found")
|
|
|
|
except:
|
|
msg = kind+' '+name+' not created or not found'
|
|
getAllObjectsCmd = azPrefix + 'kubectl get '+kind+' -n '+namespace
|
|
if not azPrefix == "":
|
|
getAllObjectsCmd = azPrefix + "'{getAllObjectsCmd}'" # add extra set of quotes
|
|
cmd = + "'" + cmd + "'"
|
|
foundObjects = os.popen().read()
|
|
suffix = f"resources of type {kind}: {foundObjects}"
|
|
sys.exit(msg + " " + suffix)
|
|
|
|
if kind == 'Deployment':
|
|
RESULT, msg = verifyDeployment(
|
|
k8_object, parsedArgs)
|
|
if kind == 'Service':
|
|
RESULT, msg = verifyService(
|
|
k8_object, parsedArgs)
|
|
if kind == 'Ingress':
|
|
RESULT, msg = verifyIngress(k8_object, parsedArgs)
|
|
if kind == "TrafficSplit":
|
|
RESULT, msg = verifyTSObject(k8_object, parsedArgs)
|
|
|
|
if not RESULT:
|
|
sys.exit(f"{kind} {name} failed check: {msg}")
|
|
|
|
print('Test passed')
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|