github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/internal/agent/hooks/argocd-application.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // SPDX-FileCopyrightText: 2021-Present The Jackal Authors
     3  
     4  // Package hooks contains the mutation hooks for the Jackal agent.
     5  package hooks
     6  
     7  import (
     8  	"encoding/json"
     9  	"fmt"
    10  
    11  	"github.com/Racer159/jackal/src/config/lang"
    12  	"github.com/Racer159/jackal/src/internal/agent/operations"
    13  	"github.com/Racer159/jackal/src/internal/agent/state"
    14  	"github.com/Racer159/jackal/src/pkg/message"
    15  	"github.com/Racer159/jackal/src/pkg/transform"
    16  	"github.com/Racer159/jackal/src/types"
    17  	"github.com/defenseunicorns/pkg/helpers"
    18  	v1 "k8s.io/api/admission/v1"
    19  )
    20  
    21  // Source represents a subset of the Argo Source object needed for Jackal Git URL mutations
    22  type Source struct {
    23  	RepoURL string `json:"repoURL"`
    24  }
    25  
    26  // ArgoApplication represents a subset of the Argo Application object needed for Jackal Git URL mutations
    27  type ArgoApplication struct {
    28  	Spec struct {
    29  		Source  Source   `json:"source"`
    30  		Sources []Source `json:"sources"`
    31  	} `json:"spec"`
    32  }
    33  
    34  var (
    35  	jackalState *types.JackalState
    36  	patches     []operations.PatchOperation
    37  	isPatched   bool
    38  	isCreate    bool
    39  	isUpdate    bool
    40  )
    41  
    42  // NewApplicationMutationHook creates a new instance of the ArgoCD Application mutation hook.
    43  func NewApplicationMutationHook() operations.Hook {
    44  	message.Debug("hooks.NewApplicationMutationHook()")
    45  	return operations.Hook{
    46  		Create: mutateApplication,
    47  		Update: mutateApplication,
    48  	}
    49  }
    50  
    51  // mutateApplication mutates the git repository url to point to the repository URL defined in the JackalState.
    52  func mutateApplication(r *v1.AdmissionRequest) (result *operations.Result, err error) {
    53  
    54  	isCreate = r.Operation == v1.Create
    55  	isUpdate = r.Operation == v1.Update
    56  
    57  	patches = []operations.PatchOperation{}
    58  
    59  	// Form the jackalState.GitServer.Address from the jackalState
    60  	if jackalState, err = state.GetJackalStateFromAgentPod(); err != nil {
    61  		return nil, fmt.Errorf(lang.AgentErrGetState, err)
    62  	}
    63  
    64  	message.Debugf("Using the url of (%s) to mutate the ArgoCD Application", jackalState.GitServer.Address)
    65  
    66  	// parse to simple struct to read the git url
    67  	src := &ArgoApplication{}
    68  
    69  	if err = json.Unmarshal(r.Object.Raw, &src); err != nil {
    70  		return nil, fmt.Errorf(lang.ErrUnmarshal, err)
    71  	}
    72  
    73  	message.Debugf("Data %v", string(r.Object.Raw))
    74  
    75  	if src.Spec.Source != (Source{}) {
    76  		patchedURL, _ := getPatchedRepoURL(src.Spec.Source.RepoURL)
    77  		patches = populateSingleSourceArgoApplicationPatchOperations(patchedURL, patches)
    78  	}
    79  
    80  	if len(src.Spec.Sources) > 0 {
    81  		for idx, source := range src.Spec.Sources {
    82  			patchedURL, _ := getPatchedRepoURL(source.RepoURL)
    83  			patches = populateMultipleSourceArgoApplicationPatchOperations(idx, patchedURL, patches)
    84  		}
    85  	}
    86  
    87  	return &operations.Result{
    88  		Allowed:  true,
    89  		PatchOps: patches,
    90  	}, nil
    91  }
    92  
    93  func getPatchedRepoURL(repoURL string) (string, error) {
    94  	var err error
    95  	patchedURL := repoURL
    96  
    97  	// Check if this is an update operation and the hostname is different from what we have in the jackalState
    98  	// NOTE: We mutate on updates IF AND ONLY IF the hostname in the request is different from the hostname in the jackalState
    99  	// NOTE: We are checking if the hostname is different before because we do not want to potentially mutate a URL that has already been mutated.
   100  	if isUpdate {
   101  		isPatched, err = helpers.DoHostnamesMatch(jackalState.GitServer.Address, repoURL)
   102  		if err != nil {
   103  			return "", fmt.Errorf(lang.AgentErrHostnameMatch, err)
   104  		}
   105  	}
   106  
   107  	// Mutate the repoURL if necessary
   108  	if isCreate || (isUpdate && !isPatched) {
   109  		// Mutate the git URL so that the hostname matches the hostname in the Jackal state
   110  		transformedURL, err := transform.GitURL(jackalState.GitServer.Address, patchedURL, jackalState.GitServer.PushUsername)
   111  		if err != nil {
   112  			message.Warnf("Unable to transform the repoURL, using the original url we have: %s", patchedURL)
   113  		}
   114  		patchedURL = transformedURL.String()
   115  		message.Debugf("original repoURL of (%s) got mutated to (%s)", repoURL, patchedURL)
   116  	}
   117  
   118  	return patchedURL, err
   119  }
   120  
   121  // Patch updates of the Argo source spec.
   122  func populateSingleSourceArgoApplicationPatchOperations(repoURL string, patches []operations.PatchOperation) []operations.PatchOperation {
   123  	return append(patches, operations.ReplacePatchOperation("/spec/source/repoURL", repoURL))
   124  }
   125  
   126  // Patch updates of the Argo sources spec.
   127  func populateMultipleSourceArgoApplicationPatchOperations(idx int, repoURL string, patches []operations.PatchOperation) []operations.PatchOperation {
   128  	return append(patches, operations.ReplacePatchOperation(fmt.Sprintf("/spec/sources/%d/repoURL", idx), repoURL))
   129  }