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  }