open-cluster-management.io/governance-policy-propagator@v0.13.0/controllers/automation/ansible.go (about) 1 // Copyright Contributors to the Open Cluster Management project 2 3 package automation 4 5 import ( 6 "context" 7 "encoding/json" 8 "fmt" 9 "reflect" 10 "regexp" 11 "strconv" 12 "strings" 13 14 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 16 "k8s.io/apimachinery/pkg/runtime/schema" 17 "k8s.io/client-go/dynamic" 18 19 policyv1beta1 "open-cluster-management.io/governance-policy-propagator/api/v1beta1" 20 ) 21 22 const ( 23 PolicyAutomationLabel string = "policy.open-cluster-management.io/policyautomation-name" 24 PolicyAutomationGeneration string = "policy.open-cluster-management.io/policyautomation-generation" 25 // policyautomation-ResouceVersion 26 PolicyAutomationResouceV string = "policy.open-cluster-management.io/policyautomation-resource-version" 27 ) 28 29 var ansibleJobRes = schema.GroupVersionResource{ 30 Group: "tower.ansible.com", Version: "v1alpha1", 31 Resource: "ansiblejobs", 32 } 33 34 // Check any ansiblejob is made by input genteration number 35 // Returning "true" means the policy automation already created ansiblejob with the generation 36 func MatchPAGeneration(policyAutomation *policyv1beta1.PolicyAutomation, 37 dynamicClient dynamic.Interface, generation int64, 38 ) (bool, error) { 39 ansiblejobList, err := dynamicClient.Resource(ansibleJobRes).Namespace(policyAutomation.GetNamespace()).List( 40 context.TODO(), metav1.ListOptions{ 41 LabelSelector: fmt.Sprintf("%s=%s", PolicyAutomationLabel, policyAutomation.GetName()), 42 }, 43 ) 44 if err != nil { 45 log.Error(err, "Failed to get ansiblejob list") 46 47 return false, err 48 } 49 50 s := strconv.FormatInt(generation, 10) 51 52 for _, aj := range ansiblejobList.Items { 53 annotations := aj.GetAnnotations() 54 if annotations[PolicyAutomationGeneration] == s { 55 return true, nil 56 } 57 } 58 59 return false, nil 60 } 61 62 // Check any ansiblejob is made by current resourceVersion number 63 // Returning "true" means the policy automation already created ansiblejob with this resourceVersion 64 func MatchPAResouceV(policyAutomation *policyv1beta1.PolicyAutomation, 65 dynamicClient dynamic.Interface, resourceVersion string, 66 ) (bool, error) { 67 ansiblejobList, err := dynamicClient.Resource(ansibleJobRes).Namespace(policyAutomation.GetNamespace()).List( 68 context.TODO(), metav1.ListOptions{ 69 LabelSelector: fmt.Sprintf("%s=%s", PolicyAutomationLabel, policyAutomation.GetName()), 70 }, 71 ) 72 if err != nil { 73 log.Error(err, "Failed to get ansiblejob list") 74 75 return false, err 76 } 77 78 for _, aj := range ansiblejobList.Items { 79 annotations := aj.GetAnnotations() 80 if annotations[PolicyAutomationResouceV] == resourceVersion { 81 return true, nil 82 } 83 } 84 85 return false, nil 86 } 87 88 // CreateAnsibleJob creates ansiblejob with given PolicyAutomation 89 func CreateAnsibleJob(policyAutomation *policyv1beta1.PolicyAutomation, 90 dynamicClient dynamic.Interface, mode string, violationContext policyv1beta1.ViolationContext, 91 ) error { 92 ansibleJob := &unstructured.Unstructured{ 93 Object: map[string]interface{}{ 94 "apiVersion": "tower.ansible.com/v1alpha1", 95 "kind": "AnsibleJob", 96 "metadata": map[string]interface{}{ 97 "annotations": map[string]interface{}{ 98 PolicyAutomationGeneration: strconv. 99 FormatInt(policyAutomation.GetGeneration(), 10), 100 PolicyAutomationResouceV: policyAutomation.GetResourceVersion(), 101 }, 102 }, 103 "spec": map[string]interface{}{ 104 "job_template_name": policyAutomation.Spec.Automation.Name, 105 "tower_auth_secret": policyAutomation.Spec.Automation.TowerSecret, 106 "extra_vars": map[string]interface{}{}, 107 "job_ttl": 86400, // default TTL is 24 hours 108 }, 109 }, 110 } 111 112 mapExtraVars := map[string]interface{}{} 113 if policyAutomation.Spec.Automation.ExtraVars != nil { 114 // This is to translate the runtime.RawExtension to a map[string]interface{} 115 err := json.Unmarshal(policyAutomation.Spec.Automation.ExtraVars.Raw, &mapExtraVars) 116 if err != nil { 117 return err 118 } 119 } 120 121 values := reflect.ValueOf(violationContext) 122 typesOf := values.Type() 123 // add every violationContext fields into mapExtraVars as well as the empty values, 124 // or when the whole violationContext is empty 125 for i := 0; i < values.NumField(); i++ { 126 tag := typesOf.Field(i).Tag 127 value := values.Field(i).Interface() 128 129 var fieldName string 130 if tag.Get("ansibleJob") != "" { 131 fieldName = tag.Get("ansibleJob") 132 } else if tag.Get("json") != "" { 133 fieldName = strings.SplitN(tag.Get("json"), ",", 2)[0] 134 } else { 135 fieldName = typesOf.Field(i).Name 136 } 137 138 mapExtraVars[fieldName] = value 139 } 140 141 label := map[string]string{ 142 PolicyAutomationLabel: policyAutomation.GetName(), 143 } 144 ansibleJob.SetLabels(label) 145 146 ansibleJob.Object["spec"].(map[string]interface{})["extra_vars"] = mapExtraVars 147 148 if policyAutomation.Spec.Automation.JobTTL != nil { 149 ansibleJob.Object["spec"].(map[string]interface{})["job_ttl"] = *policyAutomation.Spec.Automation.JobTTL 150 } 151 152 ansibleJobRes := schema.GroupVersionResource{ 153 Group: "tower.ansible.com", Version: "v1alpha1", 154 Resource: "ansiblejobs", 155 } 156 157 // Replace all special characters with hyphens. The ansible tower API requires this. 158 nonAlphaNumerics := regexp.MustCompile("[^a-zA-Z0-9]") 159 cleanName := nonAlphaNumerics.ReplaceAllString(policyAutomation.GetName(), "-") 160 161 // Ansible tower API requires the lower case naming 162 ansibleJob.SetGenerateName(strings.ToLower(cleanName + "-" + mode + "-")) 163 ansibleJob.SetOwnerReferences([]metav1.OwnerReference{ 164 *metav1.NewControllerRef(policyAutomation, policyAutomation.GroupVersionKind()), 165 }) 166 167 _, err := dynamicClient.Resource(ansibleJobRes).Namespace(policyAutomation.GetNamespace()). 168 Create(context.TODO(), ansibleJob, metav1.CreateOptions{}) 169 if err != nil { 170 return err 171 } 172 173 return nil 174 }