github.com/freiheit-com/kuberpult@v1.24.2-0.20240328135542-315d5630abe6/services/cd-service/pkg/argocd/reposerver/reposerver.go (about) 1 /*This file is part of kuberpult. 2 3 Kuberpult is free software: you can redistribute it and/or modify 4 it under the terms of the Expat(MIT) License as published by 5 the Free Software Foundation. 6 7 Kuberpult is distributed in the hope that it will be useful, 8 but WITHOUT ANY WARRANTY; without even the implied warranty of 9 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 MIT License for more details. 11 12 You should have received a copy of the MIT License 13 along with kuberpult. If not, see <https://directory.fsf.org/wiki/License:Expat>. 14 15 Copyright 2023 freiheit.com*/ 16 17 package reposerver 18 19 import ( 20 "context" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "io/fs" 25 "time" 26 27 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" 28 argorepo "github.com/argoproj/argo-cd/v2/reposerver/apiclient" 29 "github.com/argoproj/argo-cd/v2/util/argo" 30 "github.com/argoproj/gitops-engine/pkg/utils/kube" 31 "github.com/freiheit-com/kuberpult/services/cd-service/pkg/repository" 32 "github.com/go-git/go-billy/v5/util" 33 git "github.com/libgit2/git2go/v34" 34 "google.golang.org/grpc" 35 "google.golang.org/grpc/codes" 36 "google.golang.org/grpc/status" 37 "google.golang.org/protobuf/types/known/emptypb" 38 v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 39 ) 40 41 type reposerver struct { 42 repo repository.Repository 43 config repository.RepositoryConfig 44 } 45 46 var resourceTracking argo.ResourceTracking = argo.NewResourceTracking() 47 var notImplemented error = status.Error(codes.Unimplemented, "not implemented") 48 49 // GenerateManifest implements apiclient.RepoServerServiceServer. 50 func (r *reposerver) GenerateManifest(ctx context.Context, req *argorepo.ManifestRequest) (*argorepo.ManifestResponse, error) { 51 state, _, err := r.resolve(req.Revision) 52 if err != nil { 53 return nil, err 54 } 55 bfs := state.Filesystem 56 path := req.ApplicationSource.Path 57 mn := []string{} 58 filter := func(p string) bool { return true } 59 if req.ApplicationSource.Directory != nil { 60 if req.ApplicationSource.Directory.Include != "" { 61 inc := req.ApplicationSource.Directory.Include 62 filter = func(p string) bool { return p == inc } 63 } 64 } 65 err = util.Walk(bfs, path, func(file string, info fs.FileInfo, err error) error { 66 if err != nil { 67 return err 68 } 69 if !info.IsDir() && filter(info.Name()) { 70 m, err := util.ReadFile(bfs, file) 71 if err != nil { 72 return fmt.Errorf("reading %s: %w", file, err) 73 } 74 parts, err := kube.SplitYAML(m) 75 if err != nil { 76 return err 77 } 78 for _, obj := range parts { 79 if req.AppLabelKey != "" && req.AppName != "" && !kube.IsCRD(obj) { 80 err = resourceTracking.SetAppInstance(obj, req.AppLabelKey, req.AppName, req.Namespace, v1alpha1.TrackingMethod(req.TrackingMethod)) 81 if err != nil { 82 return err 83 } 84 } 85 jsonData, err := json.Marshal(obj.Object) 86 if err != nil { 87 return err 88 } 89 mn = append(mn, string(jsonData)) 90 } 91 } 92 return nil 93 }) 94 if err != nil { 95 return nil, err 96 } 97 resp := &argorepo.ManifestResponse{ 98 Namespace: "", 99 Server: "", 100 VerifyResult: "", 101 XXX_NoUnkeyedLiteral: struct{}{}, 102 XXX_unrecognized: nil, 103 XXX_sizecache: 0, 104 Manifests: mn, 105 Revision: state.Commit.Id().String(), 106 SourceType: "Directory", 107 } 108 return resp, nil 109 } 110 111 // GenerateManifestWithFiles implements apiclient.RepoServerServiceServer. 112 func (*reposerver) GenerateManifestWithFiles(argorepo.RepoServerService_GenerateManifestWithFilesServer) error { 113 return notImplemented 114 } 115 116 // GetAppDetails implements apiclient.RepoServerServiceServer. 117 func (*reposerver) GetAppDetails(context.Context, *argorepo.RepoServerAppDetailsQuery) (*argorepo.RepoAppDetailsResponse, error) { 118 return nil, notImplemented 119 } 120 121 // GetHelmCharts implements apiclient.RepoServerServiceServer. 122 func (*reposerver) GetHelmCharts(context.Context, *argorepo.HelmChartsRequest) (*argorepo.HelmChartsResponse, error) { 123 return nil, notImplemented 124 } 125 126 // GetRevisionMetadata implements apiclient.RepoServerServiceServer. 127 func (*reposerver) GetRevisionMetadata(ctx context.Context, req *argorepo.RepoServerRevisionMetadataRequest) (*v1alpha1.RevisionMetadata, error) { 128 // It doesn't matter too much what is in here as long as we don't give an error. 129 return &v1alpha1.RevisionMetadata{ 130 Author: "", 131 Date: v1.Time{ 132 Time: time.Time{}, 133 }, 134 Tags: nil, 135 Message: "", 136 SignatureInfo: "", 137 }, nil 138 } 139 140 // ListApps implements apiclient.RepoServerServiceServer. 141 func (*reposerver) ListApps(context.Context, *argorepo.ListAppsRequest) (*argorepo.AppList, error) { 142 return nil, notImplemented 143 } 144 145 // ListPlugins implements apiclient.RepoServerServiceServer. 146 func (*reposerver) ListPlugins(context.Context, *emptypb.Empty) (*argorepo.PluginList, error) { 147 return nil, notImplemented 148 } 149 150 // ListRefs implements apiclient.RepoServerServiceServer. 151 func (*reposerver) ListRefs(context.Context, *argorepo.ListRefsRequest) (*argorepo.Refs, error) { 152 return nil, notImplemented 153 } 154 155 // ResolveRevision implements apiclient.RepoServerServiceServer. 156 func (r *reposerver) ResolveRevision(ctx context.Context, req *argorepo.ResolveRevisionRequest) (*argorepo.ResolveRevisionResponse, error) { 157 state, oid, err := r.resolve(req.AmbiguousRevision) 158 if err != nil { 159 // This looks a bit strange but argocd actually responds with a succes response if the ambiguous ref can be parsed as a git commit id even if that commit does not exists at all. 160 if serr, ok := status.FromError(err); ok && serr.Code() == codes.NotFound && oid != nil { 161 return &argorepo.ResolveRevisionResponse{ 162 XXX_NoUnkeyedLiteral: struct{}{}, 163 XXX_unrecognized: nil, 164 XXX_sizecache: 0, 165 Revision: oid.String(), 166 AmbiguousRevision: fmt.Sprintf("%s (%s)", req.AmbiguousRevision, oid), 167 }, nil 168 } 169 return nil, err 170 } 171 resp := argorepo.ResolveRevisionResponse{ 172 XXX_NoUnkeyedLiteral: struct{}{}, 173 XXX_unrecognized: nil, 174 XXX_sizecache: 0, 175 Revision: state.Commit.Id().String(), 176 AmbiguousRevision: fmt.Sprintf("%s (%s)", req.AmbiguousRevision, state.Commit.Id()), 177 } 178 return &resp, nil 179 } 180 181 func (r *reposerver) resolve(rev string) (*repository.State, *git.Oid, error) { 182 var oid *git.Oid 183 if o, err := git.NewOid(rev); err == nil { 184 oid = o 185 } else if rev != "HEAD" && rev != r.config.Branch { 186 return nil, nil, status.Error(codes.NotFound, fmt.Sprintf("unknown revision %q, I only know \"HEAD\", %q and commit hashes", rev, r.config.Branch)) 187 } 188 state, err := r.repo.StateAt(oid) 189 if err != nil { 190 var gerr *git.GitError 191 if errors.As(err, &gerr) { 192 if gerr.Code == git.ErrorCodeNotFound { 193 194 return nil, oid, status.Error(codes.NotFound, fmt.Sprintf("unknown revision %q, I only know \"HEAD\", %q and commit hashes", rev, r.config.Branch)) 195 } 196 } 197 return nil, oid, status.Error(codes.Internal, fmt.Sprintf("internal error: %s", err)) 198 } 199 return state, oid, nil 200 } 201 202 // TestRepository implements apiclient.RepoServerServiceServer. 203 func (*reposerver) TestRepository(context.Context, *argorepo.TestRepositoryRequest) (*argorepo.TestRepositoryResponse, error) { 204 return nil, notImplemented 205 } 206 207 func (*reposerver) GetGitDirectories(context.Context, *argorepo.GitDirectoriesRequest) (*argorepo.GitDirectoriesResponse, error) { 208 return nil, notImplemented 209 } 210 211 func (*reposerver) GetGitFiles(context.Context, *argorepo.GitFilesRequest) (*argorepo.GitFilesResponse, error) { 212 return nil, notImplemented 213 } 214 215 func (*reposerver) GetRevisionChartDetails(context.Context, *argorepo.RepoServerRevisionChartDetailsRequest) (*v1alpha1.ChartDetails, error) { 216 return nil, notImplemented 217 } 218 219 func New(repo repository.Repository, config repository.RepositoryConfig) argorepo.RepoServerServiceServer { 220 return &reposerver{repo, config} 221 } 222 223 func Register(s *grpc.Server, repo repository.Repository, config repository.RepositoryConfig) { 224 argorepo.RegisterRepoServerServiceServer(s, New(repo, config)) 225 }