diff --git a/litmus-portal/authentication/go.mod b/litmus-portal/authentication/go.mod index cc93cf2dd..72a1e267c 100644 --- a/litmus-portal/authentication/go.mod +++ b/litmus-portal/authentication/go.mod @@ -15,4 +15,4 @@ require ( golang.org/x/net v0.0.0-20200202094626-16171245cfb2 // indirect golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect -) \ No newline at end of file +) diff --git a/litmus-portal/authentication/pkg/models/user.go b/litmus-portal/authentication/pkg/models/user.go index 5eec494cb..59285250a 100644 --- a/litmus-portal/authentication/pkg/models/user.go +++ b/litmus-portal/authentication/pkg/models/user.go @@ -45,6 +45,7 @@ const ( var DefaultUser = &UserCredentials{ UserName: types.DefaultUserName, Password: types.DefaultUserPassword, + Role: RoleAdmin, } //PublicUserInfo displays the information of the user that is publicly available diff --git a/litmus-portal/graphql-server/go.sum b/litmus-portal/graphql-server/go.sum index ef0e6d9e3..153025a6e 100644 --- a/litmus-portal/graphql-server/go.sum +++ b/litmus-portal/graphql-server/go.sum @@ -600,7 +600,6 @@ github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z github.com/litmuschaos/chaos-operator v0.0.0-20210126054859-85bb0ad85bfa h1:lBEvg10ZPndmBUMtVaMRVCqeKnKYmjtRSg2SF4iTQ7o= github.com/litmuschaos/chaos-operator v0.0.0-20210126054859-85bb0ad85bfa/go.mod h1:Z2GpYjqXwFd8bx+kv58YEQFxynx1v9PMGCGTQFRVnFQ= github.com/litmuschaos/elves v0.0.0-20201107015738-552d74669e3c/go.mod h1:DsbHGNUq/78NZozWVVI9Q6eBei4I+JjlkkD5aibJ3MQ= -github.com/litmuschaos/litmus v0.0.0-20210302122130-471654357d2b h1:BlwWoDHEvYh8RvBBWOmPrA6RgAs+d6ujxie8RU5ZgPk= github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lpabon/godbc v0.1.1/go.mod h1:Jo9QV0cf3U6jZABgiJ2skINAXb9j8m51r07g4KI92ZA= github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH9J1c9oX6otFSgdUHwUBUizmKlrMjxWnIAjff4m04= diff --git a/litmus-portal/graphql-server/graph/generated/generated.go b/litmus-portal/graphql-server/graph/generated/generated.go index 99c9bb3c9..ab03c19c7 100644 --- a/litmus-portal/graphql-server/graph/generated/generated.go +++ b/litmus-portal/graphql-server/graph/generated/generated.go @@ -218,6 +218,7 @@ type ComplexityRoot struct { UpdateChaosWorkflow func(childComplexity int, input *model.ChaosWorkFlowInput) int UpdateDashboard func(childComplexity int, dashboard *model.UpdataDBInput) int UpdateDataSource func(childComplexity int, datasource model.DSInput) int + UpdateGitOps func(childComplexity int, config model.GitConfig) int UpdateMyHub func(childComplexity int, myhubInput model.UpdateMyHub, projectID string) int UpdatePanel func(childComplexity int, panelInput []*model.Panel) int UpdateUser func(childComplexity int, user model.UpdateUserInput) int @@ -501,6 +502,7 @@ type MutationResolver interface { GitopsNotifer(ctx context.Context, clusterInfo model.ClusterIdentity, workflowID string) (string, error) EnableGitOps(ctx context.Context, config model.GitConfig) (bool, error) DisableGitOps(ctx context.Context, projectID string) (bool, error) + UpdateGitOps(ctx context.Context, config model.GitConfig) (bool, error) CreateDataSource(ctx context.Context, datasource *model.DSInput) (*model.DSResponse, error) CreateDashBoard(ctx context.Context, dashboard *model.CreateDBInput) (string, error) UpdateDataSource(ctx context.Context, datasource model.DSInput) (*model.DSResponse, error) @@ -1543,6 +1545,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.UpdateDataSource(childComplexity, args["datasource"].(model.DSInput)), true + case "Mutation.updateGitOps": + if e.complexity.Mutation.UpdateGitOps == nil { + break + } + + args, err := ec.field_Mutation_updateGitOps_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.UpdateGitOps(childComplexity, args["config"].(model.GitConfig)), true + case "Mutation.updateMyHub": if e.complexity.Mutation.UpdateMyHub == nil { break @@ -3664,8 +3678,7 @@ type Mutation { userClusterReg(clusterInput: ClusterInput!): clusterRegResponse! @authorized #It is used to create chaosworkflow - createChaosWorkFlow(input: ChaosWorkFlowInput!): ChaosWorkFlowResponse! - @authorized + createChaosWorkFlow(input: ChaosWorkFlowInput!): ChaosWorkFlowResponse! @authorized reRunChaosWorkFlow(workflowID: String!): String! @authorized @@ -3706,8 +3719,7 @@ type Mutation { syncHub(id: ID!): [MyHubStatus!]! @authorized - updateChaosWorkflow(input: ChaosWorkFlowInput): ChaosWorkFlowResponse! - @authorized + updateChaosWorkflow(input: ChaosWorkFlowInput): ChaosWorkFlowResponse! @authorized deleteClusterReg(cluster_id: String!): String! @authorized @@ -3725,6 +3737,8 @@ type Mutation { disableGitOps(project_id: String!): Boolean! @authorized + updateGitOps(config: GitConfig!): Boolean! @authorized + # Analytics createDataSource(datasource: DSInput): DSResponse @authorized @@ -4207,6 +4221,20 @@ func (ec *executionContext) field_Mutation_updateDataSource_args(ctx context.Con return args, nil } +func (ec *executionContext) field_Mutation_updateGitOps_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 model.GitConfig + if tmp, ok := rawArgs["config"]; ok { + arg0, err = ec.unmarshalNGitConfig2githubᚗcomᚋlitmuschaosᚋlitmusᚋlitmusᚑportalᚋgraphqlᚑserverᚋgraphᚋmodelᚐGitConfig(ctx, tmp) + if err != nil { + return nil, err + } + } + args["config"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_updateMyHub_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -9145,6 +9173,67 @@ func (ec *executionContext) _Mutation_disableGitOps(ctx context.Context, field g return ec.marshalNBoolean2bool(ctx, field.Selections, res) } +func (ec *executionContext) _Mutation_updateGitOps(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Mutation", + Field: field, + Args: nil, + IsMethod: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Mutation_updateGitOps_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().UpdateGitOps(rctx, args["config"].(model.GitConfig)) + } + directive1 := func(ctx context.Context) (interface{}, error) { + if ec.directives.Authorized == nil { + return nil, errors.New("directive authorized is not implemented") + } + return ec.directives.Authorized(ctx, nil, directive0) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, err + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(bool); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be bool`, tmp) + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + func (ec *executionContext) _Mutation_createDataSource(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -19323,6 +19412,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) if out.Values[i] == graphql.Null { invalids++ } + case "updateGitOps": + out.Values[i] = ec._Mutation_updateGitOps(ctx, field) + if out.Values[i] == graphql.Null { + invalids++ + } case "createDataSource": out.Values[i] = ec._Mutation_createDataSource(ctx, field) case "createDashBoard": diff --git a/litmus-portal/graphql-server/graph/schema.graphqls b/litmus-portal/graphql-server/graph/schema.graphqls index fb86e24ec..4d79b018d 100644 --- a/litmus-portal/graphql-server/graph/schema.graphqls +++ b/litmus-portal/graphql-server/graph/schema.graphqls @@ -279,8 +279,7 @@ type Mutation { userClusterReg(clusterInput: ClusterInput!): clusterRegResponse! @authorized #It is used to create chaosworkflow - createChaosWorkFlow(input: ChaosWorkFlowInput!): ChaosWorkFlowResponse! - @authorized + createChaosWorkFlow(input: ChaosWorkFlowInput!): ChaosWorkFlowResponse! @authorized reRunChaosWorkFlow(workflowID: String!): String! @authorized @@ -321,8 +320,7 @@ type Mutation { syncHub(id: ID!): [MyHubStatus!]! @authorized - updateChaosWorkflow(input: ChaosWorkFlowInput): ChaosWorkFlowResponse! - @authorized + updateChaosWorkflow(input: ChaosWorkFlowInput): ChaosWorkFlowResponse! @authorized deleteClusterReg(cluster_id: String!): String! @authorized @@ -340,6 +338,8 @@ type Mutation { disableGitOps(project_id: String!): Boolean! @authorized + updateGitOps(config: GitConfig!): Boolean! @authorized + # Analytics createDataSource(datasource: DSInput): DSResponse @authorized diff --git a/litmus-portal/graphql-server/graph/schema.resolvers.go b/litmus-portal/graphql-server/graph/schema.resolvers.go index 983eee970..1e236e0a9 100644 --- a/litmus-portal/graphql-server/graph/schema.resolvers.go +++ b/litmus-portal/graphql-server/graph/schema.resolvers.go @@ -146,6 +146,10 @@ func (r *mutationResolver) DisableGitOps(ctx context.Context, projectID string) return gitOpsHandler.DisableGitOpsHandler(ctx, projectID) } +func (r *mutationResolver) UpdateGitOps(ctx context.Context, config model.GitConfig) (bool, error) { + return gitOpsHandler.UpdateGitOpsDetailsHandler(ctx, config) +} + func (r *mutationResolver) CreateDataSource(ctx context.Context, datasource *model.DSInput) (*model.DSResponse, error) { return analyticsHandler.CreateDataSource(datasource) } diff --git a/litmus-portal/graphql-server/pkg/database/mongodb/gitops/operations.go b/litmus-portal/graphql-server/pkg/database/mongodb/gitops/operations.go index 6d3974022..70485df96 100644 --- a/litmus-portal/graphql-server/pkg/database/mongodb/gitops/operations.go +++ b/litmus-portal/graphql-server/pkg/database/mongodb/gitops/operations.go @@ -37,8 +37,6 @@ func init() { // AddGitConfig inserts new git config for project func AddGitConfig(ctx context.Context, config *GitConfigDB) error { - ctx, cancel := context.WithTimeout(backgroundContext, timeout) - defer cancel() _, err := gitOpsCollection.InsertOne(ctx, config) if err != nil { return err @@ -48,8 +46,6 @@ func AddGitConfig(ctx context.Context, config *GitConfigDB) error { // GetGitConfig retrieves git config using project id func GetGitConfig(ctx context.Context, projectID string) (*GitConfigDB, error) { - ctx, cancel := context.WithTimeout(backgroundContext, timeout) - defer cancel() query := bson.M{"project_id": projectID} var res GitConfigDB err := gitOpsCollection.FindOne(ctx, query).Decode(&res) @@ -65,8 +61,6 @@ func GetGitConfig(ctx context.Context, projectID string) (*GitConfigDB, error) { // GetAllGitConfig retrieves all git configs from db func GetAllGitConfig(ctx context.Context) ([]GitConfigDB, error) { - ctx, cancel := context.WithTimeout(backgroundContext, timeout) - defer cancel() query := bson.D{{}} cursor, err := gitOpsCollection.Find(ctx, query) if err != nil { @@ -80,10 +74,22 @@ func GetAllGitConfig(ctx context.Context) ([]GitConfigDB, error) { return configs, nil } +// ReplaceGitConfig updates git config matching the query +func ReplaceGitConfig(ctx context.Context, query bson.D, update *GitConfigDB) error { + updateResult, err := gitOpsCollection.ReplaceOne(ctx, query, update) + if err != nil { + return err + } + + if updateResult.MatchedCount == 0 { + return errors.New("No matching git config found") + } + + return nil +} + // UpdateGitConfig update git config matching the query func UpdateGitConfig(ctx context.Context, query bson.D, update bson.D) error { - ctx, cancel := context.WithTimeout(backgroundContext, timeout) - defer cancel() updateResult, err := gitOpsCollection.UpdateOne(ctx, query, update) if err != nil { return err @@ -98,8 +104,6 @@ func UpdateGitConfig(ctx context.Context, query bson.D, update bson.D) error { // DeleteGitConfig removes git config corresponding to the given project id func DeleteGitConfig(ctx context.Context, projectID string) error { - ctx, cancel := context.WithTimeout(backgroundContext, timeout) - defer cancel() _, err := gitOpsCollection.DeleteOne(ctx, bson.M{"project_id": projectID}) if err != nil { diff --git a/litmus-portal/graphql-server/pkg/gitops/gitops.go b/litmus-portal/graphql-server/pkg/gitops/gitops.go index a10e6f03a..98b635c47 100644 --- a/litmus-portal/graphql-server/pkg/gitops/gitops.go +++ b/litmus-portal/graphql-server/pkg/gitops/gitops.go @@ -9,6 +9,7 @@ import ( "io/ioutil" "os" "path/filepath" + "strconv" "strings" "time" @@ -117,16 +118,30 @@ func (c GitConfig) setupGitRepo(user GitUser) error { if err != nil { return err } - if exists { - return nil - } - // create project dir and add config if not already present - err = os.MkdirAll(projectPath, 0755) - if err != nil { - return err + gitInfo := map[string]string{"projectID": c.ProjectID, "revision": "1"} + if exists { + data, err := ioutil.ReadFile(projectPath + "/.info") + if err != nil { + return errors.New("can't read existing git info file " + err.Error()) + } + err = json.Unmarshal(data, &gitInfo) + if err != nil { + return errors.New("can't read existing git info file " + err.Error()) + } + newRev, err := strconv.Atoi(gitInfo["revision"]) + if err != nil { + return errors.New("can't read existing git info file[failed to parse revision] " + err.Error()) + } + gitInfo["revision"] = strconv.Itoa(newRev + 1) + } else { + // create project dir and add config if not already present + err = os.MkdirAll(projectPath, 0755) + if err != nil { + return err + } } - data, err := json.Marshal(map[string]string{"projectID": c.ProjectID}) + data, err := json.Marshal(gitInfo) if err != nil { return err } @@ -146,8 +161,10 @@ func (c GitConfig) setupGitRepo(user GitUser) error { // GitClone clones the repo func (c GitConfig) GitClone() (*git.Repository, error) { // clean the local path - os.RemoveAll(c.LocalPath) - + err := os.RemoveAll(c.LocalPath) + if err != nil { + return nil, err + } auth, err := c.getAuthMethod() if err != nil { return nil, err @@ -441,8 +458,7 @@ func (c GitConfig) GetLatestCommitHash() (string, error) { } // SetupGitOps clones and sets up the repo for gitops and returns the LatestCommit -func SetupGitOps(user GitUser, config dbSchemaGitOps.GitConfigDB) (string, error) { - gitConfig := GetGitOpsConfig(config) +func SetupGitOps(user GitUser, gitConfig GitConfig) (string, error) { err := gitConfig.setupGitRepo(user) if err != nil { return "", err diff --git a/litmus-portal/graphql-server/pkg/gitops/handler/handler.go b/litmus-portal/graphql-server/pkg/gitops/handler/handler.go index 830af433b..30e90862f 100644 --- a/litmus-portal/graphql-server/pkg/gitops/handler/handler.go +++ b/litmus-portal/graphql-server/pkg/gitops/handler/handler.go @@ -30,7 +30,10 @@ import ( "github.com/litmuschaos/litmus/litmus-portal/graphql-server/pkg/gitops" ) -const timeout = time.Second * 5 +const ( + timeout = time.Second * 5 + tempPath = "/tmp/gitops_test/" +) var ( gitLock = gitops.NewGitLock() @@ -54,7 +57,7 @@ func EnableGitOpsHandler(ctx context.Context, config model.GitConfig) (bool, err log.Print("Enabling Gitops") gitDB := dbSchemaGitOps.GetGitConfigDB(config) - commit, err := gitops.SetupGitOps(gitops.GitUserFromContext(ctx), gitDB) + commit, err := gitops.SetupGitOps(gitops.GitUserFromContext(ctx), gitops.GetGitOpsConfig(gitDB)) if err != nil { return false, errors.New("Failed to setup GitOps : " + err.Error()) } @@ -87,7 +90,7 @@ func DisableGitOpsHandler(ctx context.Context, projectID string) (bool, error) { return true, nil } -// GetGitOpsDetailsHandler +// GetGitOpsDetailsHandler returns the current gitops config for the requested project func GetGitOpsDetailsHandler(ctx context.Context, projectID string) (*model.GitConfigResponse, error) { gitLock.Lock(projectID, nil) defer gitLock.Unlock(projectID, nil) @@ -123,6 +126,51 @@ func GetGitOpsDetailsHandler(ctx context.Context, projectID string) (*model.GitC return &resp, nil } +// UpdateGitOpsDetailsHandler updates an exiting gitops config for a project +func UpdateGitOpsDetailsHandler(ctx context.Context, config model.GitConfig) (bool, error) { + gitLock.Lock(config.ProjectID, nil) + defer gitLock.Unlock(config.ProjectID, nil) + + gitLock.Lock(config.RepoURL, &config.Branch) + defer gitLock.Unlock(config.RepoURL, &config.Branch) + + existingConfig, err := dbOperationsGitOps.GetGitConfig(ctx, config.ProjectID) + if err != nil { + return false, errors.New("Cannot get Git Config from DB : " + err.Error()) + } + if existingConfig == nil { + return false, errors.New("GitOps Disabled ") + } + + log.Print("Enabling Gitops") + gitDB := dbSchemaGitOps.GetGitConfigDB(config) + + gitConfig := gitops.GetGitOpsConfig(gitDB) + originalPath := gitConfig.LocalPath + gitConfig.LocalPath = tempPath + gitConfig.ProjectID + commit, err := gitops.SetupGitOps(gitops.GitUserFromContext(ctx), gitConfig) + if err != nil { + return false, errors.New("Failed to setup GitOps : " + err.Error()) + } + gitDB.LatestCommit = commit + + err = dbOperationsGitOps.ReplaceGitConfig(ctx, bson.D{{"project_id", config.ProjectID}}, &gitDB) + if err != nil { + return false, errors.New("Failed to enable GitOps in DB : " + err.Error()) + } + + err = os.RemoveAll(originalPath) + if err != nil { + return false, errors.New("Cannot remove existing repo : " + err.Error()) + } + err = os.Rename(gitConfig.LocalPath, originalPath) + if err != nil { + return false, errors.New("Cannot copy new repo : " + err.Error()) + } + + return true, nil +} + // GitOpsNotificationHandler sends workflow run request(single run workflow only) to agent on gitops notification func GitOpsNotificationHandler(ctx context.Context, clusterInfo model.ClusterIdentity, workflowID string) (string, error) { cInfo, err := cluster.VerifyCluster(clusterInfo)