blob: fc748ae55f50f1f5ba98392cd614473adba65488 [file] [log] [blame]
/*
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tasks
import (
"fmt"
"reflect"
"regexp"
"time"
"github.com/apache/incubator-devlake/core/dal"
"github.com/apache/incubator-devlake/core/errors"
"github.com/apache/incubator-devlake/core/models/domainlayer"
"github.com/apache/incubator-devlake/core/models/domainlayer/devops"
"github.com/apache/incubator-devlake/core/models/domainlayer/didgen"
"github.com/apache/incubator-devlake/core/plugin"
"github.com/apache/incubator-devlake/helpers/pluginhelper/api"
"github.com/apache/incubator-devlake/plugins/argocd/models"
)
var _ plugin.SubTaskEntryPoint = ConvertSyncOperations
var ConvertSyncOperationsMeta = plugin.SubTaskMeta{
Name: "convertSyncOperations",
EntryPoint: ConvertSyncOperations,
EnabledByDefault: true,
Description: "Convert sync operations to domain layer deployments",
DependencyTables: []string{models.ArgocdSyncOperation{}.TableName()},
ProductTables: []string{"cicd_deployments", "cicd_deployment_commits"},
}
func ConvertSyncOperations(taskCtx plugin.SubTaskContext) errors.Error {
data := taskCtx.GetData().(*ArgocdTaskData)
db := taskCtx.GetDal()
var application *models.ArgocdApplication
app := &models.ArgocdApplication{}
if err := db.First(
app,
dal.Where("connection_id = ? AND name = ?", data.Options.ConnectionId, data.Options.ApplicationName),
); err == nil {
application = app
}
cursor, err := db.Cursor(
dal.From(&models.ArgocdSyncOperation{}),
dal.Where("connection_id = ? AND application_name = ?",
data.Options.ConnectionId, data.Options.ApplicationName),
)
if err != nil {
return err
}
defer cursor.Close()
converter, err := api.NewDataConverter(api.DataConverterArgs{
RawDataSubTaskArgs: api.RawDataSubTaskArgs{
Ctx: taskCtx,
Table: RAW_SYNC_OPERATION_TABLE,
Params: models.ArgocdApiParams{
ConnectionId: data.Options.ConnectionId,
Name: data.Options.ApplicationName,
},
},
InputRowType: reflect.TypeOf(models.ArgocdSyncOperation{}),
Input: cursor,
Convert: func(inputRow interface{}) ([]interface{}, errors.Error) {
syncOp := inputRow.(*models.ArgocdSyncOperation)
results := make([]interface{}, 0, 2)
if !includeSyncOperation(syncOp, data.Options.ScopeConfig, data.RegexEnricher) {
return nil, nil
}
scopeId := didgen.NewDomainIdGenerator(&models.ArgocdApplication{}).
Generate(data.Options.ConnectionId, syncOp.ApplicationName)
deploymentId := didgen.NewDomainIdGenerator(&models.ArgocdSyncOperation{}).
Generate(data.Options.ConnectionId, syncOp.ApplicationName, syncOp.DeploymentId)
var created time.Time
switch {
case syncOp.StartedAt != nil && !syncOp.StartedAt.IsZero():
created = *syncOp.StartedAt
case syncOp.FinishedAt != nil && !syncOp.FinishedAt.IsZero():
created = *syncOp.FinishedAt
case !syncOp.CreatedAt.IsZero():
created = syncOp.CreatedAt
default:
created = time.Now()
}
if created.IsZero() || created.Year() <= 1 {
created = time.Now()
}
created = created.UTC()
deployment := &devops.CICDDeployment{
DomainEntity: domainlayer.DomainEntity{
Id: deploymentId,
},
CicdScopeId: scopeId,
Name: fmt.Sprintf("%s:%d", syncOp.ApplicationName, syncOp.DeploymentId),
DisplayTitle: syncOp.Message,
Result: convertPhaseToResult(syncOp.Phase),
Status: convertPhaseToStatus(syncOp.Phase),
OriginalStatus: syncOp.Phase,
OriginalResult: syncOp.Phase,
Environment: detectEnvironment(syncOp, application, data.Options.ScopeConfig, data.RegexEnricher),
OriginalEnvironment: syncOp.ApplicationName,
TaskDatesInfo: devops.TaskDatesInfo{
CreatedDate: created,
StartedDate: syncOp.StartedAt,
FinishedDate: syncOp.FinishedAt,
},
}
if syncOp.StartedAt != nil && syncOp.FinishedAt != nil {
duration := syncOp.FinishedAt.Sub(*syncOp.StartedAt).Seconds()
deployment.DurationSec = &duration
}
results = append(results, deployment)
if syncOp.Revision != "" {
repoUrl := deployment.Name
if application != nil && application.RepoURL != "" {
repoUrl = application.RepoURL
}
deploymentCommit := &devops.CicdDeploymentCommit{
DomainEntity: domainlayer.NewDomainEntity(deploymentId),
CicdDeploymentId: deploymentId,
CicdScopeId: scopeId,
Name: deployment.Name,
DisplayTitle: deployment.DisplayTitle,
Url: deployment.Url,
Result: deployment.Result,
Status: deployment.Status,
OriginalStatus: deployment.OriginalStatus,
OriginalResult: deployment.OriginalResult,
Environment: deployment.Environment,
OriginalEnvironment: deployment.OriginalEnvironment,
TaskDatesInfo: devops.TaskDatesInfo{
CreatedDate: created,
StartedDate: syncOp.StartedAt,
FinishedDate: syncOp.FinishedAt,
},
CommitSha: syncOp.Revision,
RepoUrl: repoUrl,
}
results = append(results, deploymentCommit)
}
return results, nil
},
})
if err != nil {
return err
}
return converter.Execute()
}
func convertPhaseToResult(phase string) string {
switch phase {
case "Succeeded":
return devops.RESULT_SUCCESS
case "Failed", "Error":
return devops.RESULT_FAILURE
case "Terminating":
return devops.RESULT_FAILURE
case "Running":
return devops.RESULT_DEFAULT
default:
return devops.RESULT_DEFAULT
}
}
func convertPhaseToStatus(phase string) string {
switch phase {
case "Succeeded", "Failed", "Error":
return devops.STATUS_DONE
case "Running", "Terminating":
return devops.STATUS_IN_PROGRESS
default:
return devops.STATUS_OTHER
}
}
func detectEnvironment(
syncOp *models.ArgocdSyncOperation,
application *models.ArgocdApplication,
config *models.ArgocdScopeConfig,
enricher *api.RegexEnricher,
) string {
if config == nil {
return devops.TESTING
}
targets := []string{syncOp.ApplicationName}
if application != nil {
targets = append(targets,
application.Name,
application.Namespace,
application.DestNamespace,
)
}
if enricher != nil {
if config.ProductionPattern != "" && enricher.ReturnNameIfMatched(devops.PRODUCTION, targets...) != "" {
return devops.PRODUCTION
}
if enricher.ReturnNameIfMatched(devops.ENV_NAME_PATTERN, targets...) != "" {
return devops.PRODUCTION
}
return devops.TESTING
}
if config.ProductionPattern != "" {
if re, err := regexp.Compile(config.ProductionPattern); err == nil && re.MatchString(syncOp.ApplicationName) {
return devops.PRODUCTION
}
}
envPattern := config.EnvNamePattern
if envPattern == "" {
envPattern = "(?i)prod(.*)"
}
if re, err := regexp.Compile(envPattern); err == nil && re.MatchString(syncOp.ApplicationName) {
return devops.PRODUCTION
}
return devops.TESTING
}
func includeSyncOperation(
syncOp *models.ArgocdSyncOperation,
config *models.ArgocdScopeConfig,
enricher *api.RegexEnricher,
) bool {
if config == nil || config.DeploymentPattern == "" {
return true
}
if enricher != nil {
return enricher.ReturnNameIfMatched(devops.DEPLOYMENT, syncOp.ApplicationName) != ""
}
re, err := regexp.Compile(config.DeploymentPattern)
if err != nil {
return true
}
return re.MatchString(syncOp.ApplicationName)
}