mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-21 23:37:31 +00:00
a7cc57822b
* fix: remove unused code * fix: remove unused code * fix: remove unused code * fix: remove unused code
483 lines
17 KiB
Go
483 lines
17 KiB
Go
package eventsourcing
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/caos/logging"
|
|
"github.com/golang/protobuf/ptypes"
|
|
|
|
"github.com/caos/zitadel/internal/cache/config"
|
|
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
|
"github.com/caos/zitadel/internal/crypto"
|
|
"github.com/caos/zitadel/internal/errors"
|
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
|
es_int "github.com/caos/zitadel/internal/eventstore"
|
|
es_models "github.com/caos/zitadel/internal/eventstore/models"
|
|
es_sdk "github.com/caos/zitadel/internal/eventstore/sdk"
|
|
"github.com/caos/zitadel/internal/id"
|
|
key_model "github.com/caos/zitadel/internal/key/model"
|
|
proj_model "github.com/caos/zitadel/internal/project/model"
|
|
"github.com/caos/zitadel/internal/project/repository/eventsourcing/model"
|
|
"github.com/caos/zitadel/internal/telemetry/tracing"
|
|
)
|
|
|
|
const (
|
|
projectOwnerRole = "PROJECT_OWNER"
|
|
projectOwnerGlobalRole = "PROJECT_OWNER_GLOBAL"
|
|
)
|
|
|
|
type ProjectEventstore struct {
|
|
es_int.Eventstore
|
|
projectCache *ProjectCache
|
|
passwordAlg crypto.HashAlgorithm
|
|
pwGenerator crypto.Generator
|
|
idGenerator id.Generator
|
|
ClientKeySize int
|
|
}
|
|
|
|
type ProjectConfig struct {
|
|
es_int.Eventstore
|
|
Cache *config.CacheConfig
|
|
}
|
|
|
|
func StartProject(conf ProjectConfig, systemDefaults sd.SystemDefaults) (*ProjectEventstore, error) {
|
|
projectCache, err := StartCache(conf.Cache)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
passwordAlg := crypto.NewBCrypt(systemDefaults.SecretGenerators.PasswordSaltCost)
|
|
pwGenerator := crypto.NewHashGenerator(systemDefaults.SecretGenerators.ClientSecretGenerator, passwordAlg)
|
|
return &ProjectEventstore{
|
|
Eventstore: conf.Eventstore,
|
|
projectCache: projectCache,
|
|
passwordAlg: passwordAlg,
|
|
pwGenerator: pwGenerator,
|
|
idGenerator: id.SonyFlakeGenerator,
|
|
ClientKeySize: int(systemDefaults.SecretGenerators.ClientKeySize),
|
|
}, nil
|
|
}
|
|
|
|
func (es *ProjectEventstore) ProjectByID(ctx context.Context, id string) (*proj_model.Project, error) {
|
|
project := es.projectCache.getProject(id)
|
|
|
|
query, err := ProjectByIDQuery(project.AggregateID, project.Sequence)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = es_sdk.Filter(ctx, es.FilterEvents, project.AppendEvents, query)
|
|
if err != nil && !(caos_errs.IsNotFound(err) && project.Sequence != 0) {
|
|
return nil, err
|
|
}
|
|
if project.State == int32(proj_model.ProjectStateRemoved) {
|
|
return nil, caos_errs.ThrowNotFound(nil, "EVENT-dG8ie", "Errors.Project.NotFound")
|
|
}
|
|
es.projectCache.cacheProject(project)
|
|
return model.ProjectToModel(project), nil
|
|
}
|
|
|
|
func (es *ProjectEventstore) ProjectEventsByID(ctx context.Context, id string, sequence uint64) ([]*es_models.Event, error) {
|
|
query, err := ProjectByIDQuery(id, sequence)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return es.FilterEvents(ctx, query)
|
|
}
|
|
|
|
func (es *ProjectEventstore) ProjectChanges(ctx context.Context, id string, lastSequence uint64, limit uint64, sortAscending bool) (*proj_model.ProjectChanges, error) {
|
|
query := ChangesQuery(id, lastSequence, limit, sortAscending)
|
|
|
|
events, err := es.Eventstore.FilterEvents(context.Background(), query)
|
|
if err != nil {
|
|
logging.Log("EVENT-ZRffs").WithError(err).Warn("eventstore unavailable")
|
|
return nil, errors.ThrowInternal(err, "EVENT-328b1", "Errors.Internal")
|
|
}
|
|
if len(events) == 0 {
|
|
return nil, caos_errs.ThrowNotFound(nil, "EVENT-FpQqK", "Errors.Changes.NotFound")
|
|
}
|
|
|
|
changes := make([]*proj_model.ProjectChange, len(events))
|
|
|
|
for i, event := range events {
|
|
creationDate, err := ptypes.TimestampProto(event.CreationDate)
|
|
logging.Log("EVENT-qxIR7").OnError(err).Debug("unable to parse timestamp")
|
|
change := &proj_model.ProjectChange{
|
|
ChangeDate: creationDate,
|
|
EventType: event.Type.String(),
|
|
ModifierId: event.EditorUser,
|
|
Sequence: event.Sequence,
|
|
}
|
|
|
|
if event.Data != nil {
|
|
var data interface{}
|
|
if strings.Contains(change.EventType, "application") {
|
|
data = new(model.Application)
|
|
} else {
|
|
data = new(model.Project)
|
|
}
|
|
err = json.Unmarshal(event.Data, data)
|
|
logging.Log("EVENT-NCkpN").OnError(err).Debug("unable to unmarshal data")
|
|
change.Data = data
|
|
}
|
|
|
|
changes[i] = change
|
|
if lastSequence < event.Sequence {
|
|
lastSequence = event.Sequence
|
|
}
|
|
}
|
|
|
|
return &proj_model.ProjectChanges{
|
|
Changes: changes,
|
|
LastSequence: lastSequence,
|
|
}, nil
|
|
}
|
|
|
|
func ChangesQuery(projectID string, latestSequence, limit uint64, sortAscending bool) *es_models.SearchQuery {
|
|
query := es_models.NewSearchQuery().
|
|
AggregateTypeFilter(model.ProjectAggregate)
|
|
if !sortAscending {
|
|
query.OrderDesc()
|
|
}
|
|
|
|
query.LatestSequenceFilter(latestSequence).
|
|
AggregateIDFilter(projectID).
|
|
SetLimit(limit)
|
|
return query
|
|
}
|
|
|
|
func (es *ProjectEventstore) AddApplication(ctx context.Context, app *proj_model.Application) (*proj_model.Application, error) {
|
|
if app == nil || !app.IsValid(true) {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9eidw", "Errors.Project.App.Invalid")
|
|
}
|
|
existingProject, err := es.ProjectByID(ctx, app.AggregateID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
app.AppID, err = es.idGenerator.Next()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var stringPw string
|
|
if app.OIDCConfig != nil {
|
|
app.OIDCConfig.AppID = app.AppID
|
|
err := app.OIDCConfig.GenerateNewClientID(es.idGenerator, existingProject)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stringPw, err = app.OIDCConfig.GenerateClientSecretIfNeeded(es.pwGenerator)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if app.APIConfig != nil {
|
|
app.APIConfig.AppID = app.AppID
|
|
err := app.APIConfig.GenerateNewClientID(es.idGenerator, existingProject)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stringPw, err = app.APIConfig.GenerateClientSecretIfNeeded(es.pwGenerator)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
repoProject := model.ProjectFromModel(existingProject)
|
|
repoApp := model.AppFromModel(app)
|
|
|
|
addAggregate := ApplicationAddedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoApp)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, addAggregate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
es.projectCache.cacheProject(repoProject)
|
|
if _, a := model.GetApplication(repoProject.Applications, app.AppID); a != nil {
|
|
converted := model.AppToModel(a)
|
|
if converted.OIDCConfig != nil {
|
|
converted.OIDCConfig.ClientSecretString = stringPw
|
|
converted.OIDCConfig.FillCompliance()
|
|
}
|
|
if converted.APIConfig != nil {
|
|
converted.APIConfig.ClientSecretString = stringPw
|
|
}
|
|
return converted, nil
|
|
}
|
|
return nil, caos_errs.ThrowInternal(nil, "EVENT-GvPct", "Errors.Internal")
|
|
}
|
|
|
|
func (es *ProjectEventstore) ApplicationChanges(ctx context.Context, projectID string, appID string, lastSequence uint64, limit uint64, sortAscending bool) (*proj_model.ApplicationChanges, error) {
|
|
query := ChangesQuery(projectID, lastSequence, limit, sortAscending)
|
|
|
|
events, err := es.Eventstore.FilterEvents(ctx, query)
|
|
if err != nil {
|
|
logging.Log("EVENT-ZRffs").WithError(err).Warn("eventstore unavailable")
|
|
return nil, errors.ThrowInternal(err, "EVENT-sw6Ku", "Errors.Internal")
|
|
}
|
|
if len(events) == 0 {
|
|
return nil, caos_errs.ThrowNotFound(nil, "EVENT-9IHLP", "Errors.Changes.NotFound")
|
|
}
|
|
|
|
result := make([]*proj_model.ApplicationChange, 0)
|
|
for _, event := range events {
|
|
if !strings.Contains(event.Type.String(), "application") || event.Data == nil {
|
|
continue
|
|
}
|
|
|
|
app := new(model.Application)
|
|
err := json.Unmarshal(event.Data, app)
|
|
logging.Log("EVENT-GIiKD").OnError(err).Debug("unable to unmarshal data")
|
|
if app.AppID != appID {
|
|
continue
|
|
}
|
|
|
|
creationDate, err := ptypes.TimestampProto(event.CreationDate)
|
|
logging.Log("EVENT-MJzeN").OnError(err).Debug("unable to parse timestamp")
|
|
|
|
result = append(result, &proj_model.ApplicationChange{
|
|
ChangeDate: creationDate,
|
|
EventType: event.Type.String(),
|
|
ModifierId: event.EditorUser,
|
|
Sequence: event.Sequence,
|
|
Data: app,
|
|
})
|
|
if lastSequence < event.Sequence {
|
|
lastSequence = event.Sequence
|
|
}
|
|
}
|
|
|
|
return &proj_model.ApplicationChanges{
|
|
Changes: result,
|
|
LastSequence: lastSequence,
|
|
}, nil
|
|
}
|
|
|
|
func (es *ProjectEventstore) ChangeAPIConfig(ctx context.Context, config *proj_model.APIConfig) (*proj_model.APIConfig, error) {
|
|
if config == nil || !config.IsValid() {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-SDg54", "Errors.Project.APIConfigInvalid")
|
|
}
|
|
existingProject, err := es.ProjectByID(ctx, config.AggregateID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var app *proj_model.Application
|
|
if _, app = existingProject.GetApp(config.AppID); app == nil {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Rgu63", "Errors.Project.AppNotExisting")
|
|
}
|
|
if app.Type != proj_model.AppTypeAPI {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-RHj63", "Errors.Project.AppIsNotAPI")
|
|
}
|
|
repoProject := model.ProjectFromModel(existingProject)
|
|
repoConfig := model.APIConfigFromModel(config)
|
|
|
|
projectAggregate := APIConfigChangedAggregate(es.Eventstore.AggregateCreator(), repoProject, repoConfig)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
es.projectCache.cacheProject(repoProject)
|
|
if _, a := model.GetApplication(repoProject.Applications, app.AppID); a != nil {
|
|
return model.APIConfigToModel(a.APIConfig), nil
|
|
}
|
|
return nil, caos_errs.ThrowInternal(nil, "EVENT-aebn5", "Errors.Internal")
|
|
}
|
|
|
|
func (es *ProjectEventstore) ChangeOIDCConfigSecret(ctx context.Context, projectID, appID string) (*proj_model.OIDCConfig, error) {
|
|
if appID == "" {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-7ue34", "Errors.Project.App.OIDCConfigInvalid")
|
|
}
|
|
existingProject, err := es.ProjectByID(ctx, projectID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var app *proj_model.Application
|
|
if _, app = existingProject.GetApp(appID); app == nil {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-9odi4", "Errors.Project.App.NotExisting")
|
|
}
|
|
if app.Type != proj_model.AppTypeOIDC {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-dile4", "Errors.Project.App.IsNotOIDC")
|
|
}
|
|
if app.OIDCConfig.AuthMethodType == proj_model.OIDCAuthMethodTypeNone || app.OIDCConfig.AuthMethodType == proj_model.OIDCAuthMethodTypePrivateKeyJWT {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-GDrg2", "Errors.Project.OIDCAuthMethodNoSecret")
|
|
}
|
|
repoProject := model.ProjectFromModel(existingProject)
|
|
|
|
stringPw, err := app.OIDCConfig.GenerateNewClientSecret(es.pwGenerator)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
projectAggregate := OIDCConfigSecretChangedAggregate(es.Eventstore.AggregateCreator(), repoProject, appID, app.OIDCConfig.ClientSecret)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
es.projectCache.cacheProject(repoProject)
|
|
|
|
if _, a := model.GetApplication(repoProject.Applications, app.AppID); a != nil {
|
|
config := model.OIDCConfigToModel(a.OIDCConfig)
|
|
config.ClientSecretString = stringPw
|
|
return config, nil
|
|
}
|
|
|
|
return nil, caos_errs.ThrowInternal(nil, "EVENT-dk87s", "Errors.Internal")
|
|
}
|
|
|
|
func (es *ProjectEventstore) ChangeAPIConfigSecret(ctx context.Context, projectID, appID string) (*proj_model.APIConfig, error) {
|
|
if appID == "" {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-sdfb3", "Errors.Project.APIConfigInvalid")
|
|
}
|
|
existingProject, err := es.ProjectByID(ctx, projectID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var app *proj_model.Application
|
|
if _, app = existingProject.GetApp(appID); app == nil {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-ADbg3", "Errors.Project.AppNotExisting")
|
|
}
|
|
if app.Type != proj_model.AppTypeAPI {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Ntwqw", "Errors.Project.AppIsNotAPI")
|
|
}
|
|
if app.APIConfig.AuthMethodType != proj_model.APIAuthMethodTypeBasic {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-HW4tw", "Errors.Project.APIAuthMethodNoSecret")
|
|
}
|
|
repoProject := model.ProjectFromModel(existingProject)
|
|
|
|
stringPw, err := app.APIConfig.GenerateNewClientSecret(es.pwGenerator)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
projectAggregate := APIConfigSecretChangedAggregate(es.Eventstore.AggregateCreator(), repoProject, appID, app.APIConfig.ClientSecret)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, projectAggregate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
es.projectCache.cacheProject(repoProject)
|
|
|
|
if _, a := model.GetApplication(repoProject.Applications, app.AppID); a != nil {
|
|
config := model.APIConfigToModel(a.APIConfig)
|
|
config.ClientSecretString = stringPw
|
|
return config, nil
|
|
}
|
|
|
|
return nil, caos_errs.ThrowInternal(nil, "EVENT-HBfju", "Errors.Internal")
|
|
}
|
|
|
|
func (es *ProjectEventstore) VerifyOIDCClientSecret(ctx context.Context, projectID, appID string, secret string) (err error) {
|
|
ctx, span := tracing.NewSpan(ctx)
|
|
defer func() { span.EndWithError(err) }()
|
|
if appID == "" {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-H3RT2", "Errors.Project.RequiredFieldsMissing")
|
|
}
|
|
existingProject, err := es.ProjectByID(ctx, projectID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var app *proj_model.Application
|
|
if _, app = existingProject.GetApp(appID); app == nil {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-D6hba", "Errors.Project.AppNoExisting")
|
|
}
|
|
if app.Type != proj_model.AppTypeOIDC {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-huywq", "Errors.Project.App.IsNotOIDC")
|
|
}
|
|
|
|
ctx, spanHash := tracing.NewSpan(ctx)
|
|
err = crypto.CompareHash(app.OIDCConfig.ClientSecret, []byte(secret), es.passwordAlg)
|
|
spanHash.EndWithError(err)
|
|
if err == nil {
|
|
err = es.setOIDCClientSecretCheckResult(ctx, existingProject, app.AppID, OIDCClientSecretCheckSucceededAggregate)
|
|
logging.Log("EVENT-AE1vf").OnError(err).Warn("could not push event OIDCClientSecretCheckSucceeded")
|
|
return nil
|
|
}
|
|
err = es.setOIDCClientSecretCheckResult(ctx, existingProject, app.AppID, OIDCClientSecretCheckFailedAggregate)
|
|
logging.Log("EVENT-GD1gh").OnError(err).Warn("could not push event OIDCClientSecretCheckFailed")
|
|
return caos_errs.ThrowInvalidArgument(nil, "EVENT-wg24q", "Errors.Project.OIDCSecretInvalid")
|
|
}
|
|
|
|
func (es *ProjectEventstore) setOIDCClientSecretCheckResult(ctx context.Context, project *proj_model.Project, appID string, check func(*es_models.AggregateCreator, *model.Project, string) es_sdk.AggregateFunc) error {
|
|
repoProject := model.ProjectFromModel(project)
|
|
agg := check(es.AggregateCreator(), repoProject, appID)
|
|
err := es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, agg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
es.projectCache.cacheProject(repoProject)
|
|
return nil
|
|
}
|
|
|
|
func (es *ProjectEventstore) AddClientKey(ctx context.Context, key *proj_model.ClientKey) (*proj_model.ClientKey, error) {
|
|
existingProject, err := es.ProjectByID(ctx, key.AggregateID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var app *proj_model.Application
|
|
if _, app = existingProject.GetApp(key.ApplicationID); app == nil {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Dbf32", "Errors.Project.AppNoExisting")
|
|
}
|
|
if (app.OIDCConfig == nil || app.OIDCConfig != nil && app.OIDCConfig.AuthMethodType != proj_model.OIDCAuthMethodTypePrivateKeyJWT) &&
|
|
(app.APIConfig == nil || app.APIConfig != nil && app.APIConfig.AuthMethodType != proj_model.APIAuthMethodTypePrivateKeyJWT) {
|
|
return nil, caos_errs.ThrowPreconditionFailed(nil, "EVENT-Dff54", "Errors.Project.AuthMethodNoPrivateKeyJWT")
|
|
}
|
|
if app.OIDCConfig != nil {
|
|
key.ClientID = app.OIDCConfig.ClientID
|
|
}
|
|
if app.APIConfig != nil {
|
|
key.ClientID = app.APIConfig.ClientID
|
|
}
|
|
key.KeyID, err = es.idGenerator.Next()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if key.ExpirationDate.IsZero() {
|
|
key.ExpirationDate, err = key_model.DefaultExpiration()
|
|
if err != nil {
|
|
logging.Log("EVENT-Adgf2").WithError(err).Warn("unable to set default date")
|
|
return nil, errors.ThrowInternal(err, "EVENT-j68fg", "Errors.Internal")
|
|
}
|
|
}
|
|
if key.ExpirationDate.Before(time.Now()) {
|
|
return nil, errors.ThrowInvalidArgument(nil, "EVENT-C6YV5", "Errors.MachineKey.ExpireBeforeNow")
|
|
}
|
|
|
|
repoProject := model.ProjectFromModel(existingProject)
|
|
repoKey := model.ClientKeyFromModel(key)
|
|
err = repoKey.GenerateClientKeyPair(es.ClientKeySize)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
agg := OIDCApplicationKeyAddedAggregate(es.AggregateCreator(), repoProject, repoKey)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, agg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
es.projectCache.cacheProject(repoProject)
|
|
|
|
return model.ClientKeyToModel(repoKey), nil
|
|
}
|
|
|
|
func (es *ProjectEventstore) RemoveApplicationKey(ctx context.Context, projectID, applicationID, keyID string) error {
|
|
existingProject, err := es.ProjectByID(ctx, projectID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var app *proj_model.Application
|
|
if _, app = existingProject.GetApp(applicationID); app == nil {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-ADfzz", "Errors.Project.AppNotExisting")
|
|
}
|
|
if app.Type != proj_model.AppTypeOIDC {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-ADffh", "Errors.Project.AppIsNotOIDC")
|
|
}
|
|
if _, key := app.GetKey(keyID); key == nil {
|
|
return caos_errs.ThrowPreconditionFailed(nil, "EVENT-D2Sff", "Errors.Project.AppKeyNotExisting")
|
|
}
|
|
repoProject := model.ProjectFromModel(existingProject)
|
|
agg := OIDCApplicationKeyRemovedAggregate(es.AggregateCreator(), repoProject, keyID)
|
|
err = es_sdk.Push(ctx, es.PushAggregates, repoProject.AppendEvents, agg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
es.projectCache.cacheProject(repoProject)
|
|
return nil
|
|
}
|