github.com/Racer159/jackal@v0.32.7-0.20240401174413-0bd2339e4f2e/src/internal/agent/hooks/argocd-repository.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/base64"
     9  	"encoding/json"
    10  	"fmt"
    11  
    12  	"github.com/Racer159/jackal/src/config/lang"
    13  	"github.com/Racer159/jackal/src/internal/agent/operations"
    14  	"github.com/Racer159/jackal/src/internal/agent/state"
    15  	"github.com/Racer159/jackal/src/pkg/message"
    16  	"github.com/Racer159/jackal/src/pkg/transform"
    17  	"github.com/Racer159/jackal/src/types"
    18  	"github.com/defenseunicorns/pkg/helpers"
    19  	v1 "k8s.io/api/admission/v1"
    20  )
    21  
    22  // ArgoRepository represents a subset of the Argo Repository object needed for Jackal Git URL mutations
    23  type ArgoRepository struct {
    24  	Data struct {
    25  		URL string `json:"url"`
    26  	}
    27  }
    28  
    29  // NewRepositoryMutationHook creates a new instance of the ArgoCD Repository mutation hook.
    30  func NewRepositoryMutationHook() operations.Hook {
    31  	message.Debug("hooks.NewRepositoryMutationHook()")
    32  	return operations.Hook{
    33  		Create: mutateRepository,
    34  		Update: mutateRepository,
    35  	}
    36  }
    37  
    38  // mutateRepository mutates the git repository URL to point to the repository URL defined in the JackalState.
    39  func mutateRepository(r *v1.AdmissionRequest) (result *operations.Result, err error) {
    40  
    41  	var (
    42  		jackalState *types.JackalState
    43  		patches     []operations.PatchOperation
    44  		isPatched   bool
    45  
    46  		isCreate = r.Operation == v1.Create
    47  		isUpdate = r.Operation == v1.Update
    48  	)
    49  
    50  	// Form the jackalState.GitServer.Address from the jackalState
    51  	if jackalState, err = state.GetJackalStateFromAgentPod(); err != nil {
    52  		return nil, fmt.Errorf(lang.AgentErrGetState, err)
    53  	}
    54  
    55  	message.Debugf("Using the url of (%s) to mutate the ArgoCD Repository Secret", jackalState.GitServer.Address)
    56  
    57  	// parse to simple struct to read the git url
    58  	src := &ArgoRepository{}
    59  
    60  	if err = json.Unmarshal(r.Object.Raw, &src); err != nil {
    61  		return nil, fmt.Errorf(lang.ErrUnmarshal, err)
    62  	}
    63  	decodedURL, err := base64.StdEncoding.DecodeString(src.Data.URL)
    64  	if err != nil {
    65  		message.Fatalf("Error decoding URL from Repository Secret %s", src.Data.URL)
    66  	}
    67  	src.Data.URL = string(decodedURL)
    68  	patchedURL := src.Data.URL
    69  
    70  	// Check if this is an update operation and the hostname is different from what we have in the jackalState
    71  	// NOTE: We mutate on updates IF AND ONLY IF the hostname in the request is different from the hostname in the jackalState
    72  	// 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.
    73  	if isUpdate {
    74  		isPatched, err = helpers.DoHostnamesMatch(jackalState.GitServer.Address, src.Data.URL)
    75  		if err != nil {
    76  			return nil, fmt.Errorf(lang.AgentErrHostnameMatch, err)
    77  		}
    78  	}
    79  
    80  	// Mutate the repoURL if necessary
    81  	if isCreate || (isUpdate && !isPatched) {
    82  		// Mutate the git URL so that the hostname matches the hostname in the Jackal state
    83  		transformedURL, err := transform.GitURL(jackalState.GitServer.Address, patchedURL, jackalState.GitServer.PushUsername)
    84  		if err != nil {
    85  			message.Warnf("Unable to transform the url, using the original url we have: %s", patchedURL)
    86  		}
    87  		patchedURL = transformedURL.String()
    88  		message.Debugf("original url of (%s) got mutated to (%s)", src.Data.URL, patchedURL)
    89  	}
    90  
    91  	// Patch updates of the repo spec
    92  	patches = populateArgoRepositoryPatchOperations(patchedURL, jackalState.GitServer.PullPassword)
    93  
    94  	return &operations.Result{
    95  		Allowed:  true,
    96  		PatchOps: patches,
    97  	}, nil
    98  }
    99  
   100  // Patch updates of the Argo Repository Secret.
   101  func populateArgoRepositoryPatchOperations(repoURL string, jackalGitPullPassword string) []operations.PatchOperation {
   102  	var patches []operations.PatchOperation
   103  	patches = append(patches, operations.ReplacePatchOperation("/data/url", base64.StdEncoding.EncodeToString([]byte(repoURL))))
   104  	patches = append(patches, operations.ReplacePatchOperation("/data/username", base64.StdEncoding.EncodeToString([]byte(types.JackalGitReadUser))))
   105  	patches = append(patches, operations.ReplacePatchOperation("/data/password", base64.StdEncoding.EncodeToString([]byte(jackalGitPullPassword))))
   106  
   107  	return patches
   108  }