As part of the CI/CD, I need to update a deployment on argo, and then run system tests on this deployment. Instead of doing this manually, I've created a small Go code to handle the version replace, the sync after the update, and the waiting for the sync completion. The code is below. Feel free to copy and get inspiration from it.
type Test struct {
automationbase.AutomationBase
webClient *web.Client
token string
}
func TestValidation(_ *testing.T) {
t := Test{
AutomationBase: *automationbase.ProduceAutomationBase(),
webClient: web.CreateClient(0),
}
t.AutomationWorker = t.check
t.RunAutomation()
}
func (t *Test) check() {
t.login()
summary := t.getSummary()
updatedParameters := t.updateVersionInSummary(summary)
t.setSummary(updatedParameters)
t.sync()
for {
time.Sleep(5 * time.Second)
summary = t.getSummary()
if t.isSynced(summary) {
t.Log("sync done")
return
}
}
}
func (t *Test) getEnvSecure(
key string,
) string {
value := os.Getenv(key)
if value == "" {
kiterr.RaiseIfError(fmt.Errorf("%v environment variable is empty", key))
}
return value
}
func (t *Test) login() {
password := t.getEnvSecure("PIB_PASSWORD")
body := map[string]string{
"username": "admin",
"password": password,
}
response := t.sendRequestToArgo("POST", "/api/v1/session", body)
responseMap := t.interfaceJsonFromString(response)
token := responseMap["token"]
t.token = token.(string)
}
func (t *Test) sync() string {
version := t.getEnvSecure("PIB_VERSION")
fullVersion := fmt.Sprintf("%v-dev-%v", project, version)
data := fmt.Sprintf(`{"revision":"%v","prune":false,"dryRun":false,"strategy":{"hook":{"force":false}},"resources":null,"syncOptions":{"items":["CreateNamespace=true"]}}`, fullVersion)
bodyJson := t.interfaceJsonFromString(data)
return t.sendRequestToArgo("POST", "/api/v1/applications/"+project+"/sync", bodyJson)
}
func (t *Test) getSummary() string {
return t.sendRequestToArgo("GET", "/api/v1/applications/"+project, nil)
}
func (t *Test) setSummary(
parameters map[string]interface{},
) {
t.sendRequestToArgo("PUT", "/api/v1/applications/"+project, parameters)
}
func (t *Test) updateVersionInSummary(
summary string,
) map[string]interface{} {
parametersMap := t.interfaceJsonFromString(summary)
spec := t.interfaceJsonFromMap(parametersMap, "spec")
source := t.interfaceJsonFromMap(spec, "source")
helm := t.interfaceJsonFromMap(source, "helm")
version := t.getEnvSecure("PIB_VERSION")
fullVersion := fmt.Sprintf("%v-dev-%v", project, version)
source["targetRevision"] = fullVersion
helm["values"] = fmt.Sprintf("global:\n image:\n version: :dev-%v\n\n", version)
return parametersMap
}
func (t *Test) sendRequestToArgo(
method string,
path string,
body interface{},
) string {
var requestHeaders *web.SectionHeaders
if t.token != "" {
cookie := fmt.Sprintf("argocd.token=%v", t.token)
requestHeaders = web.ProduceSectionHeaders()
requestHeaders.SetHeader("Cookie", cookie)
}
t.Log("sending %v %v with body:\n%v", method, path, body)
fullPath := fmt.Sprintf("http://pib8.cloud-ng.net:31390%v", path)
var response string
t.webClient.SendRequestWithHeaders(
method,
fullPath,
body,
requestHeaders,
&response,
)
if len(response) > 0 {
jsonData := t.interfaceJsonFromString(response)
t.Log("response is:\n%v", kitjson.ObjectToStringIndented(jsonData))
}
// don't rush argo
time.Sleep(time.Second)
return response
}
func (t *Test) interfaceJsonFromMap(
input map[string]interface{},
key string,
) map[string]interface{} {
value := input[key]
if value == nil {
kiterr.RaiseIfError(fmt.Errorf("key not found: %v", key))
}
valueMap, ok := value.(map[string]interface{})
if !ok {
kiterr.RaiseIfError(fmt.Errorf("convert key %v failed for value:\n%v", key, kitjson.ObjectToStringIndented(value)))
}
return valueMap
}
func (t *Test) interfaceJsonArrayFromMap(
input map[string]interface{},
key string,
) []interface{} {
value := input[key]
if value == nil {
kiterr.RaiseIfError(fmt.Errorf("key not found: %v", key))
}
array, ok := value.([]interface{})
if !ok {
kiterr.RaiseIfError(fmt.Errorf("convert key %v failed", key))
}
return array
}
func (t *Test) interfaceJsonFromString(
data string,
) map[string]interface{} {
var jsonMap map[string]interface{}
err := json.Unmarshal([]byte(data), &jsonMap)
if err != nil {
kiterr.RaiseIfError(fmt.Errorf("unmarshalling failed: %v", data))
}
return jsonMap
}
func (t *Test) isSynced(
summary string,
) bool {
parametersMap := t.interfaceJsonFromString(summary)
status := t.interfaceJsonFromMap(parametersMap, "status")
if !t.isSyncOperationsDone(status) {
return false
}
if !t.isResourcesSyncDone(status) {
return false
}
return true
}
func (t *Test) isResourcesSyncDone(status map[string]interface{}) bool {
resources := t.interfaceJsonArrayFromMap(status, "resources")
for _, resource := range resources {
resourceMap, ok := resource.(map[string]interface{})
if !ok {
kiterr.RaiseIfError(fmt.Errorf("convert failed"))
}
kind := resourceMap["kind"]
if kind == "Job" || kind == "Role" || kind == "RoleBinding" {
// never synced
continue
}
resourceStatus := resourceMap["status"]
if resourceStatus != nil && resourceStatus != "Synced" {
t.Log("pending sync for:\n%v", kitjson.ObjectToStringIndented(resourceMap))
return false
}
if kind == "Deployment" || kind == "StatefulSet" {
health := t.interfaceJsonFromMap(resourceMap, "health")
heathStatus := health["status"]
if heathStatus != "Healthy" {
t.Log("pending sync for:\n%v", kitjson.ObjectToStringIndented(resourceMap))
return false
}
}
}
return true
}
func (t *Test) isSyncOperationsDone(status map[string]interface{}) bool {
operationState := t.interfaceJsonFromMap(status, "operationState")
phase := operationState["phase"]
if phase == "Succeeded" {
return true
}
t.Log("sync state %v", phase)
return false
}