diff --git a/commitserver/apiclient/commit.pb.go b/commitserver/apiclient/commit.pb.go index 03b10480c9ab1..c5f417f3a03c0 100644 --- a/commitserver/apiclient/commit.pb.go +++ b/commitserver/apiclient/commit.pb.go @@ -43,10 +43,12 @@ type CommitHydratedManifestsRequest struct { // Paths contains the paths to write hydrated manifests to, along with the manifests and commands to execute. Paths []*PathDetails `protobuf:"bytes,6,rep,name=paths,proto3" json:"paths,omitempty"` // DryCommitMetadata contains metadata about the DRY commit, such as the author and committer. - DryCommitMetadata *v1alpha1.RevisionMetadata `protobuf:"bytes,7,opt,name=dryCommitMetadata,proto3" json:"dryCommitMetadata,omitempty"` - XXX_NoUnkeyedLiteral struct{} `json:"-"` - XXX_unrecognized []byte `json:"-"` - XXX_sizecache int32 `json:"-"` + DryCommitMetadata *v1alpha1.RevisionMetadata `protobuf:"bytes,7,opt,name=dryCommitMetadata,proto3" json:"dryCommitMetadata,omitempty"` + // ReadmeMessage is the message content for README template updates. + ReadmeMessage string `protobuf:"bytes,8,opt,name=readmeMessage,proto3" json:"readmeMessage,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` } func (m *CommitHydratedManifestsRequest) Reset() { *m = CommitHydratedManifestsRequest{} } @@ -131,6 +133,13 @@ func (m *CommitHydratedManifestsRequest) GetDryCommitMetadata() *v1alpha1.Revisi return nil } +func (m *CommitHydratedManifestsRequest) GetReadmeMessage() string { + if m != nil { + return m.ReadmeMessage + } + return "" +} + // PathDetails holds information about hydrated manifests to be written to a particular path in the hydrated manifests // commit. type PathDetails struct { @@ -307,37 +316,38 @@ func init() { func init() { proto.RegisterFile("commitserver/commit/commit.proto", fileDescriptor_cf3a3abbc35e3069) } var fileDescriptor_cf3a3abbc35e3069 = []byte{ - // 479 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x53, 0xc1, 0x6e, 0xd3, 0x40, - 0x10, 0x95, 0xeb, 0x34, 0x90, 0x49, 0x7b, 0x60, 0x0f, 0xd4, 0xca, 0xc1, 0xb5, 0x2c, 0x0e, 0xb9, - 0xb0, 0x56, 0x13, 0xc1, 0x8d, 0x4b, 0xc3, 0xa1, 0x42, 0xb4, 0x20, 0xe7, 0x86, 0x2a, 0xa1, 0xa9, - 0xbd, 0xd8, 0x4b, 0x63, 0xef, 0xb2, 0xbb, 0xb1, 0x64, 0x89, 0xaf, 0xe1, 0x6b, 0x38, 0xf2, 0x09, - 0x28, 0x5f, 0x82, 0xbc, 0xb6, 0x69, 0x02, 0x0a, 0x39, 0x70, 0xf2, 0xce, 0xbc, 0xf1, 0x7b, 0xb3, - 0xf3, 0x76, 0x20, 0x48, 0x44, 0x51, 0x70, 0xa3, 0x99, 0xaa, 0x98, 0x8a, 0xda, 0xa0, 0xfb, 0x50, - 0xa9, 0x84, 0x11, 0x93, 0xb7, 0x19, 0x37, 0xf9, 0xfa, 0x8e, 0x26, 0xa2, 0x88, 0x50, 0x65, 0x42, - 0x2a, 0xf1, 0xd9, 0x1e, 0x9e, 0x27, 0x69, 0x54, 0xcd, 0x23, 0x79, 0x9f, 0x45, 0x28, 0xb9, 0x8e, - 0x50, 0xca, 0x15, 0x4f, 0xd0, 0x70, 0x51, 0x46, 0xd5, 0x05, 0xae, 0x64, 0x8e, 0x17, 0x51, 0xc6, - 0x4a, 0xa6, 0xd0, 0xb0, 0xb4, 0x65, 0x0b, 0xbf, 0xb9, 0xe0, 0x2f, 0x2c, 0xfd, 0x55, 0x9d, 0x5a, - 0xe0, 0x1a, 0x4b, 0xfe, 0x89, 0x69, 0xa3, 0x63, 0xf6, 0x65, 0xcd, 0xb4, 0x21, 0xb7, 0x30, 0x50, - 0x4c, 0x0a, 0xcf, 0x09, 0x9c, 0xe9, 0x78, 0x76, 0x45, 0x1f, 0xf4, 0x69, 0xaf, 0x6f, 0x0f, 0x1f, - 0x93, 0x94, 0x56, 0x73, 0x2a, 0xef, 0x33, 0xda, 0xe8, 0xd3, 0x2d, 0x7d, 0xda, 0xeb, 0xd3, 0x98, - 0x49, 0xa1, 0xb9, 0x11, 0xaa, 0x8e, 0x2d, 0x2b, 0xf1, 0x01, 0x74, 0x5d, 0x26, 0x97, 0x0a, 0xcb, - 0x24, 0xf7, 0x8e, 0x02, 0x67, 0x3a, 0x8a, 0xb7, 0x32, 0x24, 0x84, 0x13, 0x83, 0x2a, 0x63, 0xa6, - 0xab, 0x70, 0x6d, 0xc5, 0x4e, 0x8e, 0x3c, 0x85, 0x61, 0xaa, 0xea, 0x65, 0x8e, 0xde, 0xc0, 0xa2, - 0x5d, 0x44, 0x9e, 0xc1, 0x69, 0x3b, 0xba, 0x6b, 0xa6, 0x35, 0x66, 0xcc, 0x3b, 0xb6, 0xf0, 0x6e, - 0x92, 0x84, 0x70, 0x2c, 0xd1, 0xe4, 0xda, 0x1b, 0x06, 0xee, 0x74, 0x3c, 0x3b, 0xa1, 0xef, 0xd1, - 0xe4, 0xaf, 0x99, 0x41, 0xbe, 0xd2, 0x71, 0x0b, 0x91, 0xaf, 0xf0, 0x24, 0x55, 0xf5, 0xa2, 0xfb, - 0xcf, 0x60, 0x8a, 0x06, 0xbd, 0x47, 0x76, 0x20, 0x37, 0xff, 0x3b, 0x90, 0x8a, 0x6b, 0x2e, 0xca, - 0x9e, 0x35, 0xfe, 0x5b, 0x28, 0x5c, 0xc3, 0x78, 0xab, 0x27, 0x42, 0x60, 0xd0, 0x74, 0x65, 0x0d, - 0x19, 0xc5, 0xf6, 0x4c, 0x5e, 0xc2, 0xa8, 0xe8, 0x8d, 0xf3, 0x8e, 0xec, 0x45, 0x3c, 0xfa, 0xa7, - 0xa5, 0xfd, 0xa5, 0x1e, 0x4a, 0xc9, 0x04, 0x1e, 0x37, 0xd3, 0xc0, 0x32, 0xd5, 0x9e, 0x1b, 0xb8, - 0xd3, 0x51, 0xfc, 0x3b, 0x0e, 0x5f, 0xc1, 0xd9, 0x1e, 0x86, 0xc6, 0x95, 0x9e, 0xe3, 0xcd, 0xf2, - 0xdd, 0x4d, 0xd7, 0xca, 0x4e, 0x2e, 0x5c, 0xc0, 0xf9, 0xde, 0x97, 0xa5, 0xa5, 0x28, 0x35, 0x23, - 0x01, 0x8c, 0xf3, 0x0e, 0x6c, 0xdc, 0x6b, 0x59, 0xb6, 0x53, 0xb3, 0x02, 0x4e, 0x5b, 0x92, 0x25, - 0x53, 0x15, 0x4f, 0x18, 0xb9, 0x85, 0xb3, 0x3d, 0xac, 0xe4, 0x9c, 0xfe, 0xfb, 0x25, 0x4f, 0x02, - 0x7a, 0xa0, 0xa1, 0xcb, 0xc5, 0xf7, 0x8d, 0xef, 0xfc, 0xd8, 0xf8, 0xce, 0xcf, 0x8d, 0xef, 0x7c, - 0x78, 0x71, 0x60, 0xd5, 0x76, 0x76, 0x15, 0x25, 0x4f, 0x56, 0x9c, 0x95, 0xe6, 0x6e, 0x68, 0x57, - 0x6b, 0xfe, 0x2b, 0x00, 0x00, 0xff, 0xff, 0x21, 0x3b, 0xa8, 0x3c, 0xcc, 0x03, 0x00, 0x00, + // 490 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x53, 0xcd, 0x6e, 0xd3, 0x40, + 0x10, 0x96, 0x9b, 0x34, 0x34, 0x9b, 0xf6, 0xc0, 0x1e, 0xa8, 0x95, 0x83, 0x6b, 0x59, 0x1c, 0x72, + 0x61, 0xad, 0x26, 0x82, 0x1b, 0x97, 0x86, 0x43, 0x85, 0x68, 0x41, 0xce, 0x0d, 0x55, 0x42, 0x53, + 0x7b, 0xb0, 0x97, 0xc6, 0xde, 0x65, 0x77, 0x63, 0xc9, 0x12, 0x0f, 0xd8, 0x23, 0x8f, 0x80, 0xf2, + 0x24, 0xc8, 0x6b, 0x9b, 0xc6, 0xa0, 0xd0, 0x43, 0x4f, 0x9e, 0xf9, 0x66, 0xfc, 0x7d, 0xf3, 0xb3, + 0x43, 0xfc, 0x58, 0xe4, 0x39, 0x37, 0x1a, 0x55, 0x89, 0x2a, 0x6c, 0x9c, 0xf6, 0xc3, 0xa4, 0x12, + 0x46, 0x4c, 0x3f, 0xa4, 0xdc, 0x64, 0x9b, 0x5b, 0x16, 0x8b, 0x3c, 0x04, 0x95, 0x0a, 0xa9, 0xc4, + 0x37, 0x6b, 0xbc, 0x8a, 0x93, 0xb0, 0x5c, 0x84, 0xf2, 0x2e, 0x0d, 0x41, 0x72, 0x1d, 0x82, 0x94, + 0x6b, 0x1e, 0x83, 0xe1, 0xa2, 0x08, 0xcb, 0x73, 0x58, 0xcb, 0x0c, 0xce, 0xc3, 0x14, 0x0b, 0x54, + 0x60, 0x30, 0x69, 0xd8, 0x82, 0xfb, 0x01, 0xf1, 0x96, 0x96, 0xfe, 0xb2, 0x4a, 0x6c, 0xe0, 0x0a, + 0x0a, 0xfe, 0x15, 0xb5, 0xd1, 0x11, 0x7e, 0xdf, 0xa0, 0x36, 0xf4, 0x86, 0x0c, 0x15, 0x4a, 0xe1, + 0x3a, 0xbe, 0x33, 0x9b, 0xcc, 0x2f, 0xd9, 0x83, 0x3e, 0xeb, 0xf4, 0xad, 0xf1, 0x25, 0x4e, 0x58, + 0xb9, 0x60, 0xf2, 0x2e, 0x65, 0xb5, 0x3e, 0xdb, 0xd1, 0x67, 0x9d, 0x3e, 0x8b, 0x50, 0x0a, 0xcd, + 0x8d, 0x50, 0x55, 0x64, 0x59, 0xa9, 0x47, 0x88, 0xae, 0x8a, 0xf8, 0x42, 0x41, 0x11, 0x67, 0xee, + 0x81, 0xef, 0xcc, 0xc6, 0xd1, 0x0e, 0x42, 0x03, 0x72, 0x6c, 0x40, 0xa5, 0x68, 0xda, 0x8c, 0x81, + 0xcd, 0xe8, 0x61, 0xf4, 0x05, 0x19, 0x25, 0xaa, 0x5a, 0x65, 0xe0, 0x0e, 0x6d, 0xb4, 0xf5, 0xe8, + 0x4b, 0x72, 0xd2, 0x8c, 0xee, 0x0a, 0xb5, 0x86, 0x14, 0xdd, 0x43, 0x1b, 0xee, 0x83, 0x34, 0x20, + 0x87, 0x12, 0x4c, 0xa6, 0xdd, 0x91, 0x3f, 0x98, 0x4d, 0xe6, 0xc7, 0xec, 0x13, 0x98, 0xec, 0x1d, + 0x1a, 0xe0, 0x6b, 0x1d, 0x35, 0x21, 0xfa, 0x83, 0x3c, 0x4f, 0x54, 0xb5, 0x6c, 0xff, 0x33, 0x90, + 0x80, 0x01, 0xf7, 0x99, 0x1d, 0xc8, 0xf5, 0x53, 0x07, 0x52, 0x72, 0xcd, 0x45, 0xd1, 0xb1, 0x46, + 0xff, 0x0a, 0xd5, 0x7d, 0x28, 0x84, 0x24, 0xc7, 0xae, 0x8f, 0xa3, 0xa6, 0x8f, 0x1e, 0x18, 0x6c, + 0xc8, 0x64, 0xa7, 0x72, 0x4a, 0xc9, 0xb0, 0xae, 0xdd, 0xae, 0x6d, 0x1c, 0x59, 0x9b, 0xbe, 0x21, + 0xe3, 0xbc, 0x5b, 0xaf, 0x7b, 0x60, 0xdb, 0x75, 0xd9, 0xdf, 0x8b, 0xef, 0x5a, 0x7f, 0x48, 0xa5, + 0x53, 0x72, 0x54, 0xcf, 0x0c, 0x8a, 0x44, 0xbb, 0x03, 0x7f, 0x30, 0x1b, 0x47, 0x7f, 0xfc, 0xe0, + 0x2d, 0x39, 0xdd, 0xc3, 0x50, 0xef, 0xae, 0xe3, 0x78, 0xbf, 0xfa, 0x78, 0xdd, 0x96, 0xd2, 0xc3, + 0x82, 0x25, 0x39, 0xdb, 0xfb, 0xfe, 0xb4, 0x14, 0x85, 0x46, 0xea, 0x93, 0x49, 0xd6, 0x06, 0xeb, + 0x1d, 0x37, 0x2c, 0xbb, 0xd0, 0x3c, 0x27, 0x27, 0x0d, 0xc9, 0x0a, 0x55, 0xc9, 0x63, 0xa4, 0x37, + 0xe4, 0x74, 0x0f, 0x2b, 0x3d, 0x63, 0xff, 0x7f, 0xef, 0x53, 0x9f, 0x3d, 0x52, 0xd0, 0xc5, 0xf2, + 0x7e, 0xeb, 0x39, 0x3f, 0xb7, 0x9e, 0xf3, 0x6b, 0xeb, 0x39, 0x9f, 0x5f, 0x3f, 0x72, 0x90, 0xbd, + 0x8b, 0x06, 0xc9, 0xe3, 0x35, 0xc7, 0xc2, 0xdc, 0x8e, 0xec, 0x01, 0x2e, 0x7e, 0x07, 0x00, 0x00, + 0xff, 0xff, 0x65, 0xc9, 0x7b, 0x6b, 0xf2, 0x03, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -446,6 +456,13 @@ func (m *CommitHydratedManifestsRequest) MarshalToSizedBuffer(dAtA []byte) (int, i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if len(m.ReadmeMessage) > 0 { + i -= len(m.ReadmeMessage) + copy(dAtA[i:], m.ReadmeMessage) + i = encodeVarintCommit(dAtA, i, uint64(len(m.ReadmeMessage))) + i-- + dAtA[i] = 0x42 + } if m.DryCommitMetadata != nil { { size, err := m.DryCommitMetadata.MarshalToSizedBuffer(dAtA[:i]) @@ -687,6 +704,10 @@ func (m *CommitHydratedManifestsRequest) Size() (n int) { l = m.DryCommitMetadata.Size() n += 1 + l + sovCommit(uint64(l)) } + l = len(m.ReadmeMessage) + if l > 0 { + n += 1 + l + sovCommit(uint64(l)) + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -1022,6 +1043,38 @@ func (m *CommitHydratedManifestsRequest) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ReadmeMessage", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommit + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCommit + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCommit + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ReadmeMessage = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipCommit(dAtA[iNdEx:]) diff --git a/commitserver/commit/commit.go b/commitserver/commit/commit.go index ffc9e157d2205..38fb7a7f4a359 100644 --- a/commitserver/commit/commit.go +++ b/commitserver/commit/commit.go @@ -47,29 +47,6 @@ type hydratorMetadataFile struct { References []v1alpha1.RevisionReference `json:"references,omitempty"` } -// TODO: make this configurable via ConfigMap. -var manifestHydrationReadmeTemplate = `# Manifest Hydration - -To hydrate the manifests in this repository, run the following commands: - -` + "```shell" + ` -git clone {{ .RepoURL }} -# cd into the cloned directory -git checkout {{ .DrySHA }} -{{ range $command := .Commands -}} -{{ $command }} -{{ end -}}` + "```" + ` -{{ if .References -}} - -## References - -{{ range $ref := .References -}} -{{ if $ref.Commit -}} -* [{{ $ref.Commit.SHA | mustRegexFind "[0-9a-f]+" | trunc 7 }}]({{ $ref.Commit.RepoURL }}): {{ $ref.Commit.Subject }} ({{ $ref.Commit.Author }}) -{{ end -}} -{{ end -}} -{{ end -}}` - // CommitHydratedManifests handles a commit request. It clones the repository, checks out the sync branch, checks out // the target branch, clears the repository contents, writes the manifests to the repository, commits the changes, and // pushes the changes. It returns the hydrated revision SHA and an error if one occurred. @@ -120,6 +97,7 @@ func (s *Service) handleCommitRequest(logCtx *log.Entry, r *apiclient.CommitHydr if r.Repo == nil { return "", "", errors.New("repo is required") } + if r.Repo.Repo == "" { return "", "", errors.New("repo URL is required") } @@ -179,7 +157,7 @@ func (s *Service) handleCommitRequest(logCtx *log.Entry, r *apiclient.CommitHydr } logCtx.Debug("Writing manifests") - err = WriteForPaths(root, r.Repo.Repo, r.DrySha, r.DryCommitMetadata, r.Paths) + err = WriteForPaths(root, r.Repo.Repo, r.DrySha, r.DryCommitMetadata, r.Paths, r.ReadmeMessage) if err != nil { return "", "", fmt.Errorf("failed to write manifests: %w", err) } diff --git a/commitserver/commit/commit.proto b/commitserver/commit/commit.proto index a2424d73f0cc5..5866a6daf4ec9 100644 --- a/commitserver/commit/commit.proto +++ b/commitserver/commit/commit.proto @@ -20,6 +20,8 @@ message CommitHydratedManifestsRequest { repeated PathDetails paths = 6; // DryCommitMetadata contains metadata about the DRY commit, such as the author and committer. github.com.argoproj.argo_cd.v3.pkg.apis.application.v1alpha1.RevisionMetadata dryCommitMetadata = 7; + // ReadmeMessage is the message content for README template updates. + string readmeMessage = 8; } // PathDetails holds information about hydrated manifests to be written to a particular path in the hydrated manifests diff --git a/commitserver/commit/hydratorhelper.go b/commitserver/commit/hydratorhelper.go index 523b049a13d04..40e4b26cbcc5b 100644 --- a/commitserver/commit/hydratorhelper.go +++ b/commitserver/commit/hydratorhelper.go @@ -33,7 +33,7 @@ func init() { // WriteForPaths writes the manifests, hydrator.metadata, and README.md files for each path in the provided paths. It // also writes a root-level hydrator.metadata file containing the repo URL and dry SHA. -func WriteForPaths(root *os.Root, repoUrl, drySha string, dryCommitMetadata *appv1.RevisionMetadata, paths []*apiclient.PathDetails) error { //nolint:revive //FIXME(var-naming) +func WriteForPaths(root *os.Root, repoUrl, drySha string, dryCommitMetadata *appv1.RevisionMetadata, paths []*apiclient.PathDetails, rawReadmeTemplate string) error { //nolint:revive //FIXME(var-naming) hydratorMetadata, err := hydrator.GetCommitMetadata(repoUrl, drySha, dryCommitMetadata) if err != nil { return fmt.Errorf("failed to retrieve hydrator metadata: %w", err) @@ -83,7 +83,7 @@ func WriteForPaths(root *os.Root, repoUrl, drySha string, dryCommitMetadata *app } // Write README - err = writeReadme(root, hydratePath, hydratorMetadata) + err = writeReadme(root, hydratePath, hydratorMetadata, rawReadmeTemplate) if err != nil { return fmt.Errorf("failed to write readme: %w", err) } @@ -111,8 +111,8 @@ func writeMetadata(root *os.Root, dirPath string, metadata hydrator.HydratorComm } // writeReadme writes the readme to the README.md file. -func writeReadme(root *os.Root, dirPath string, metadata hydrator.HydratorCommitMetadata) error { - readmeTemplate, err := template.New("readme").Funcs(sprigFuncMap).Parse(manifestHydrationReadmeTemplate) +func writeReadme(root *os.Root, dirPath string, metadata hydrator.HydratorCommitMetadata, rawReadmeTemplate string) error { + readmeTemplate, err := template.New("readme").Funcs(sprigFuncMap).Parse(rawReadmeTemplate) if err != nil { return fmt.Errorf("failed to parse readme template: %w", err) } diff --git a/commitserver/commit/hydratorhelper_test.go b/commitserver/commit/hydratorhelper_test.go index 5838ee28b0307..cf871ee15ee5d 100644 --- a/commitserver/commit/hydratorhelper_test.go +++ b/commitserver/commit/hydratorhelper_test.go @@ -19,6 +19,7 @@ import ( "github.com/argoproj/argo-cd/v3/commitserver/apiclient" appsv1 "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1" "github.com/argoproj/argo-cd/v3/util/hydrator" + "github.com/argoproj/argo-cd/v3/util/settings" ) // tempRoot creates a temporary directory and returns an os.Root object for it. @@ -93,7 +94,7 @@ Argocd-reference-commit-sha: abc123 }, } - err := WriteForPaths(root, repoURL, drySha, metadata, paths) + err := WriteForPaths(root, repoURL, drySha, metadata, paths, settings.ManifestHydrationReadmeTemplate) require.NoError(t, err) // Check if the top-level hydrator.metadata exists and contains the repo URL and dry SHA @@ -188,7 +189,7 @@ func TestWriteReadme(t *testing.T) { }, } - err = writeReadme(root, "", metadata) + err = writeReadme(root, "", metadata, settings.ManifestHydrationReadmeTemplate) require.NoError(t, err) readmePath := filepath.Join(root.Name(), "README.md") diff --git a/controller/hydrator/hydrator.go b/controller/hydrator/hydrator.go index 3850bea1a48d1..ce22dd8184825 100644 --- a/controller/hydrator/hydrator.go +++ b/controller/hydrator/hydrator.go @@ -69,6 +69,9 @@ type Dependencies interface { // GetHydratorCommitMessageTemplate gets the configured template for rendering commit messages. GetHydratorCommitMessageTemplate() (string, error) + + // GetHydratorReadmeMessageTemplate gets the configured template for rendering README messages. + GetHydratorReadmeMessageTemplate() (string, error) } // Hydrator is the main struct that implements the hydration logic. It uses the Dependencies interface to access the @@ -425,6 +428,12 @@ func (h *Hydrator) hydrate(logCtx *log.Entry, apps []*appv1.Application, project return targetRevision, "", errors, fmt.Errorf("failed to get hydrator commit templated message: %w", errMsg) } + // get the readme message template + readmeTemplate, err := h.dependencies.GetHydratorReadmeMessageTemplate() + if err != nil { + return "", "", errors, fmt.Errorf("failed to get hydrated readme message template: %w", err) + } + manifestsRequest := commitclient.CommitHydratedManifestsRequest{ Repo: repo, SyncBranch: syncBranch, @@ -433,6 +442,7 @@ func (h *Hydrator) hydrate(logCtx *log.Entry, apps []*appv1.Application, project CommitMessage: commitMessage, Paths: paths, DryCommitMetadata: revisionMetadata, + ReadmeMessage: readmeTemplate, } closer, commitService, err := h.commitClientset.NewCommitServerClient() diff --git a/controller/hydrator/hydrator_test.go b/controller/hydrator/hydrator_test.go index d36978ec0f6df..4333f18975cc5 100644 --- a/controller/hydrator/hydrator_test.go +++ b/controller/hydrator/hydrator_test.go @@ -641,6 +641,7 @@ func TestProcessHydrationQueueItem_SuccessfulHydration(t *testing.T) { rc.On("GetRevisionMetadata", mock.Anything, mock.Anything).Return(nil, nil).Once() d.On("GetWriteCredentials", mock.Anything, "https://example.com/repo", "test-project").Return(nil, nil).Once() d.On("GetHydratorCommitMessageTemplate").Return("commit message", nil).Once() + d.On("GetHydratorReadmeMessageTemplate").Return("readme message", nil).Once() cc.On("CommitHydratedManifests", mock.Anything, mock.Anything).Return(&commitclient.CommitHydratedManifestsResponse{HydratedSha: "def456"}, nil).Once() h.ProcessHydrationQueueItem(hydrationKey) @@ -801,6 +802,7 @@ func TestHydrator_hydrate_Success(t *testing.T) { }) d.On("GetWriteCredentials", mock.Anything, readRepo.Repo, proj.Name).Return(writeRepo, nil) d.On("GetHydratorCommitMessageTemplate").Return("commit message", nil) + d.On("GetHydratorReadmeMessageTemplate").Return("readme message", nil) cc.On("CommitHydratedManifests", mock.Anything, mock.Anything).Return(&commitclient.CommitHydratedManifestsResponse{HydratedSha: "hydrated123"}, nil).Run(func(args mock.Arguments) { r := args.Get(1).(*commitclient.CommitHydratedManifestsRequest) assert.Equal(t, "commit message", r.CommitMessage) @@ -1009,6 +1011,7 @@ func TestHydrator_hydrate_CommitHydratedManifestsError(t *testing.T) { rc.On("GetRevisionMetadata", mock.Anything, mock.Anything).Return(&v1alpha1.RevisionMetadata{}, nil) d.On("GetWriteCredentials", mock.Anything, mock.Anything, mock.Anything).Return(&v1alpha1.Repository{Repo: "https://example.com/repo"}, nil) d.On("GetHydratorCommitMessageTemplate").Return("commit message", nil) + d.On("GetHydratorReadmeMessageTemplate").Return("readme message", nil) cc.On("CommitHydratedManifests", mock.Anything, mock.Anything).Return(nil, errors.New("commit error")) logCtx := log.NewEntry(log.StandardLogger()) diff --git a/controller/hydrator/mocks/Dependencies.go b/controller/hydrator/mocks/Dependencies.go index 1bb4a67f817f8..c0110b6f79928 100644 --- a/controller/hydrator/mocks/Dependencies.go +++ b/controller/hydrator/mocks/Dependencies.go @@ -134,6 +134,59 @@ func (_c *Dependencies_GetHydratorCommitMessageTemplate_Call) RunAndReturn(run f return _c } +// GetHydratorReadmeMessageTemplate provides a mock function for the type Dependencies +func (_mock *Dependencies) GetHydratorReadmeMessageTemplate() (string, error) { + ret := _mock.Called() + + if len(ret) == 0 { + panic("no return value specified for GetHydratorReadmeMessageTemplate") + } + + var r0 string + var r1 error + if returnFunc, ok := ret.Get(0).(func() (string, error)); ok { + return returnFunc() + } + if returnFunc, ok := ret.Get(0).(func() string); ok { + r0 = returnFunc() + } else { + r0 = ret.Get(0).(string) + } + if returnFunc, ok := ret.Get(1).(func() error); ok { + r1 = returnFunc() + } else { + r1 = ret.Error(1) + } + return r0, r1 +} + +// Dependencies_GetHydratorReadmeMessageTemplate_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetHydratorReadmeMessageTemplate' +type Dependencies_GetHydratorReadmeMessageTemplate_Call struct { + *mock.Call +} + +// GetHydratorReadmeMessageTemplate is a helper method to define mock.On call +func (_e *Dependencies_Expecter) GetHydratorReadmeMessageTemplate() *Dependencies_GetHydratorReadmeMessageTemplate_Call { + return &Dependencies_GetHydratorReadmeMessageTemplate_Call{Call: _e.mock.On("GetHydratorReadmeMessageTemplate")} +} + +func (_c *Dependencies_GetHydratorReadmeMessageTemplate_Call) Run(run func()) *Dependencies_GetHydratorReadmeMessageTemplate_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Dependencies_GetHydratorReadmeMessageTemplate_Call) Return(s string, err error) *Dependencies_GetHydratorReadmeMessageTemplate_Call { + _c.Call.Return(s, err) + return _c +} + +func (_c *Dependencies_GetHydratorReadmeMessageTemplate_Call) RunAndReturn(run func() (string, error)) *Dependencies_GetHydratorReadmeMessageTemplate_Call { + _c.Call.Return(run) + return _c +} + // GetProcessableAppProj provides a mock function for the type Dependencies func (_mock *Dependencies) GetProcessableAppProj(app *v1alpha1.Application) (*v1alpha1.AppProject, error) { ret := _mock.Called(app) diff --git a/controller/hydrator_dependencies.go b/controller/hydrator_dependencies.go index 9ab759b2faf3c..f6fc83c299e6c 100644 --- a/controller/hydrator_dependencies.go +++ b/controller/hydrator_dependencies.go @@ -106,3 +106,12 @@ func (ctrl *ApplicationController) GetHydratorCommitMessageTemplate() (string, e return sourceHydratorCommitMessageKey, nil } + +func (ctrl *ApplicationController) GetHydratorReadmeMessageTemplate() (string, error) { + sourceHydratorReadmeMessageKey, err := ctrl.settingsMgr.GetHydratorReadmeTemplate() + if err != nil { + return "", fmt.Errorf("failed to get sourceHydrator README message template key: %w", err) + } + + return sourceHydratorReadmeMessageKey, nil +} diff --git a/controller/hydrator_dependencies_test.go b/controller/hydrator_dependencies_test.go index 8b51db5bf25ca..ec31f1fc8b8c1 100644 --- a/controller/hydrator_dependencies_test.go +++ b/controller/hydrator_dependencies_test.go @@ -121,3 +121,48 @@ func TestGetHydratorCommitMessageTemplate(t *testing.T) { require.NoError(t, err) assert.NotEmpty(t, tmpl) } + +func TestGetHydratorReadmeMessageTemplate_WhenTemplateIsNotDefined_FallbackToDefault(t *testing.T) { + cm := test.NewConfigMap() + cmBytes, _ := json.Marshal(cm) + + data := fakeData{ + manifestResponse: &apiclient.ManifestResponse{ + Manifests: []string{string(cmBytes)}, + Namespace: test.FakeDestNamespace, + Server: test.FakeClusterURL, + Revision: "abc123", + }, + } + + ctrl := newFakeControllerWithResync(&data, time.Minute, nil, errors.New("this should not be called")) + + tmpl, err := ctrl.GetHydratorReadmeMessageTemplate() + require.NoError(t, err) + assert.NotEmpty(t, tmpl) // fallback 되어야 함 + assert.Equal(t, settings.ManifestHydrationReadmeTemplate, tmpl) +} + +func TestGetHydratorReadmeMessageTemplate(t *testing.T) { + readmeTemplate := `hello world` + cm := test.NewFakeConfigMap() + cm.Data["sourceHydrator.readmeMessageTemplate"] = readmeTemplate + cmBytes, _ := json.Marshal(cm) + + data := fakeData{ + manifestResponse: &apiclient.ManifestResponse{ + Manifests: []string{string(cmBytes)}, + Namespace: test.FakeDestNamespace, + Server: test.FakeClusterURL, + Revision: "abc123", + }, + configMapData: cm.Data, + } + + ctrl := newFakeControllerWithResync(&data, time.Minute, nil, errors.New("this should not be called")) + + tmpl, err := ctrl.GetHydratorReadmeMessageTemplate() + require.NoError(t, err) + assert.NotEmpty(t, tmpl) + assert.Equal(t, readmeTemplate, tmpl) +} diff --git a/docs/operator-manual/argocd-cm.yaml b/docs/operator-manual/argocd-cm.yaml index 209a706048b29..582941f7ef142 100644 --- a/docs/operator-manual/argocd-cm.yaml +++ b/docs/operator-manual/argocd-cm.yaml @@ -458,3 +458,34 @@ data: {{- if .metadata.author }} Co-authored-by: {{ .metadata.author }} {{- end }} + + ### SourceHydrator readme message template. + # This template defines the content of the README.md file that is automatically + # generated during the hydration process. It can use placeholders such as + # `.RepoURL`, `.DrySHA`, and `.Commands` to provide reproducible instructions. + # + # The template is dynamically loaded from a ConfigMap, which allows customizing + # the README format depending on the environment or requirements. + sourceHydrator.readmeMessageTemplate: | + # Manifest Hydration + + To hydrate the manifests in this repository, run the following commands: + + ```shell + git clone {{ .RepoURL }} + # cd into the cloned directory + git checkout {{ .DrySHA }} + {{ range $command := .Commands -}} + {{ $command }} + {{ end -}} + ``` + + {{ if .References -}} + ## References + + {{ range $ref := .References -}} + {{ if $ref.Commit -}} + * [{{ $ref.Commit.SHA | mustRegexFind "[0-9a-f]+" | trunc 7 }}]({{ $ref.Commit.RepoURL }}): {{ $ref.Commit.Subject }} ({{ $ref.Commit.Author }}) + {{ end -}} + {{ end -}} + {{ end -}} \ No newline at end of file diff --git a/docs/user-guide/source-hydrator.md b/docs/user-guide/source-hydrator.md index 9aa8e2a069e71..bcb6defe9e165 100644 --- a/docs/user-guide/source-hydrator.md +++ b/docs/user-guide/source-hydrator.md @@ -299,6 +299,46 @@ data: {{- if .metadata.author }} Co-authored-by: {{ .metadata.author }} {{- end }} +``` + +## README Template + +The hydration README is generated using a [Go text/template](https://pkg.go.dev/text/template), optionally configured by the user via the `argocd-cm` ConfigMap. The template is rendered using the values from `hydrator.metadata` and can be multi-line to define structured documentation. This allows users to customize how the hydration process and references are documented. + +To define the README template, set the `sourceHydrator.readmeMessageTemplate` field in the `argocd-cm` ConfigMap. + +The template may also use functions from the [Sprig function library](https://github.com/Masterminds/sprig). + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: argocd-cm + namespace: argocd +data: + sourceHydrator.readmeMessageTemplate: | + # Manifest Hydration + + To hydrate the manifests in this repository, run the following commands: + + ```shell + git clone {{ .RepoURL }} + git checkout {{ .DrySHA }} + {{ range $command := .Commands }} + {{ $command }} + {{ end }} + ``` + + {{ if .References }} + ## References + + {{ range $ref := .References }} + {{ if $ref.Commit }} + * [{{ $ref.Commit.SHA | trunc 7 }}]({{ $ref.Commit.RepoURL }}): {{ $ref.Commit.Subject }} ({{ $ref.Commit.Author }}) + {{ end }} + {{ end }} + {{ end }} +``` ### Credential Templates diff --git a/util/settings/settings.go b/util/settings/settings.go index 92526a657431a..1e71e8178ab22 100644 --- a/util/settings/settings.go +++ b/util/settings/settings.go @@ -60,6 +60,28 @@ Co-authored-by: {{ .metadata.author }} {{- end }} ` +var ManifestHydrationReadmeTemplate = `# Manifest Hydration + +To hydrate the manifests in this repository, run the following commands: + +` + "```shell" + ` +git clone {{ .RepoURL }} +# cd into the cloned directory +git checkout {{ .DrySHA }} +{{ range $command := .Commands -}} +{{ $command }} +{{ end -}}` + "```" + ` +{{ if .References -}} + +## References + +{{ range $ref := .References -}} +{{ if $ref.Commit -}} +* [{{ $ref.Commit.SHA | mustRegexFind "[0-9a-f]+" | trunc 7 }}]({{ $ref.Commit.RepoURL }}): {{ $ref.Commit.Subject }} ({{ $ref.Commit.Author }}) +{{ end -}} +{{ end -}} +{{ end -}}` + // ArgoCDSettings holds in-memory runtime configuration options. type ArgoCDSettings struct { // URL is the externally facing URL users will visit to reach Argo CD. @@ -511,6 +533,8 @@ const ( settingsBinaryUrlsKey = "help.download" // settingsApplicationInstanceLabelKey is the key to configure injected app instance label key settingsSourceHydratorCommitMessageTemplateKey = "sourceHydrator.commitMessageTemplate" + // settingsSourceHydratorReadmeMessageTemplateKey is the key to configure hydrator default commit README.md template + settingsSourceHydratorReadmeMessageTemplateKey = "sourceHydrator.readmeMessageTemplate" // globalProjectsKey designates the key for global project settings globalProjectsKey = "globalProjects" // initialPasswordSecretName is the name of the secret that will hold the initial admin password @@ -798,6 +822,18 @@ func (mgr *SettingsManager) getSecrets() ([]*corev1.Secret, error) { return secrets, nil } +func (mgr *SettingsManager) GetHydratorReadmeTemplate() (string, error) { + argoCDCM, err := mgr.getConfigMap() + if err != nil { + return ManifestHydrationReadmeTemplate, err + } + readmeTemplate := argoCDCM.Data[settingsSourceHydratorReadmeMessageTemplateKey] + if readmeTemplate == "" { + return ManifestHydrationReadmeTemplate, nil + } + return readmeTemplate, nil +} + func (mgr *SettingsManager) GetResourcesFilter() (*ResourcesFilter, error) { argoCDCM, err := mgr.getConfigMap() if err != nil { @@ -1027,6 +1063,7 @@ func (mgr *SettingsManager) GetSourceHydratorCommitMessageTemplate() (string, er if err != nil { return "", err } + if argoCDCM.Data[settingsSourceHydratorCommitMessageTemplateKey] == "" { return CommitMessageTemplate, nil // in case template is not defined return default } diff --git a/util/settings/settings_test.go b/util/settings/settings_test.go index 30a8f03ce9a4f..53a66e4c1c607 100644 --- a/util/settings/settings_test.go +++ b/util/settings/settings_test.go @@ -2189,3 +2189,30 @@ func TestSettingsManager_GetAllowedNodeLabels(t *testing.T) { }) } } + +func TestGetHydratorReadmeTemplate(t *testing.T) { + t.Run("DefaultTemplate", func(t *testing.T) { + _, settingsManager := fixtures(map[string]string{}) + template, err := settingsManager.GetHydratorReadmeTemplate() + require.NoError(t, err) + assert.Equal(t, ManifestHydrationReadmeTemplate, template) + }) + + t.Run("CustomTemplateInConfigMap", func(t *testing.T) { + customTemplate := "Custom Readme Template: {{ .RepoURL }}" + _, settingsManager := fixtures(map[string]string{ + settingsSourceHydratorReadmeMessageTemplateKey: customTemplate, + }) + template, err := settingsManager.GetHydratorReadmeTemplate() + require.NoError(t, err) + assert.Equal(t, customTemplate, template) + }) + + t.Run("ConfigMapError", func(t *testing.T) { + kubeClient := fake.NewClientset() + settingsManager := NewSettingsManager(context.Background(), kubeClient, "default") + template, err := settingsManager.GetHydratorReadmeTemplate() + require.Error(t, err) + assert.Equal(t, ManifestHydrationReadmeTemplate, template) + }) +}