mirror of
https://github.com/onepanelio/onepanel.git
synced 2025-10-05 05:36:50 +08:00
Merge branch 'master' into chore/removing.unused.function
This commit is contained in:
2
go.mod
2
go.mod
@@ -15,6 +15,7 @@ require (
|
|||||||
github.com/ghodss/yaml v1.0.0
|
github.com/ghodss/yaml v1.0.0
|
||||||
github.com/go-sql-driver/mysql v1.5.0 // indirect
|
github.com/go-sql-driver/mysql v1.5.0 // indirect
|
||||||
github.com/golang/protobuf v1.4.1
|
github.com/golang/protobuf v1.4.1
|
||||||
|
github.com/google/uuid v1.1.2
|
||||||
github.com/gorilla/handlers v1.4.2
|
github.com/gorilla/handlers v1.4.2
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0
|
github.com/grpc-ecosystem/go-grpc-middleware v1.2.0
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.14.4
|
github.com/grpc-ecosystem/grpc-gateway v1.14.4
|
||||||
@@ -43,5 +44,4 @@ require (
|
|||||||
k8s.io/apimachinery v0.16.7-beta.0
|
k8s.io/apimachinery v0.16.7-beta.0
|
||||||
k8s.io/client-go v0.16.4
|
k8s.io/client-go v0.16.4
|
||||||
sigs.k8s.io/yaml v1.2.0
|
sigs.k8s.io/yaml v1.2.0
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
|
||||||
)
|
)
|
||||||
|
2
go.sum
2
go.sum
@@ -221,6 +221,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
|
|||||||
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||||
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
|
||||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||||
|
@@ -8,6 +8,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
sq "github.com/Masterminds/squirrel"
|
sq "github.com/Masterminds/squirrel"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/onepanelio/core/pkg/util/gcs"
|
"github.com/onepanelio/core/pkg/util/gcs"
|
||||||
"github.com/onepanelio/core/pkg/util/label"
|
"github.com/onepanelio/core/pkg/util/label"
|
||||||
"github.com/onepanelio/core/pkg/util/ptr"
|
"github.com/onepanelio/core/pkg/util/ptr"
|
||||||
@@ -18,8 +19,11 @@ import (
|
|||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
networking "istio.io/api/networking/v1alpha3"
|
||||||
|
"k8s.io/apimachinery/pkg/util/intstr"
|
||||||
"k8s.io/apimachinery/pkg/watch"
|
"k8s.io/apimachinery/pkg/watch"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
yaml2 "sigs.k8s.io/yaml"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -275,6 +279,15 @@ func (c *Client) injectAutomatedFields(namespace string, wf *wfv1.Workflow, opts
|
|||||||
template.Metadata.Annotations = make(map[string]string)
|
template.Metadata.Annotations = make(map[string]string)
|
||||||
}
|
}
|
||||||
template.Metadata.Annotations["sidecar.istio.io/inject"] = "false"
|
template.Metadata.Annotations["sidecar.istio.io/inject"] = "false"
|
||||||
|
//For workflows with accessible sidecars, we need istio
|
||||||
|
//Istio does not prevent the main container from stopping
|
||||||
|
for _, s := range template.Sidecars {
|
||||||
|
if s.TTY == true {
|
||||||
|
template.Metadata.Annotations["sidecar.istio.io/inject"] = "true"
|
||||||
|
//Only need one instance to require istio injection
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if template.Container != nil {
|
if template.Container != nil {
|
||||||
// Mount dev/shm
|
// Mount dev/shm
|
||||||
@@ -408,6 +421,11 @@ func (c *Client) createWorkflow(namespace string, workflowTemplateID uint64, wor
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newTemplateOrder, err := c.injectAccessForSidecars(namespace, wf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
wf.Spec.Templates = newTemplateOrder
|
||||||
createdArgoWorkflow, err := c.ArgoprojV1alpha1().Workflows(namespace).Create(wf)
|
createdArgoWorkflow, err := c.ArgoprojV1alpha1().Workflows(namespace).Create(wf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -437,6 +455,260 @@ func (c *Client) createWorkflow(namespace string, workflowTemplateID uint64, wor
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) injectAccessForSidecars(namespace string, wf *wfv1.Workflow) ([]wfv1.Template, error) {
|
||||||
|
var newTemplateOrder []wfv1.Template
|
||||||
|
taskSysSendStatusName := "sys-send-status"
|
||||||
|
taskSysSendExitStats := "sys-send-exit-stats"
|
||||||
|
for tIdx, t := range wf.Spec.Templates {
|
||||||
|
//Inject services, virtual routes
|
||||||
|
for si, s := range t.Sidecars {
|
||||||
|
//If TTY is true, sidecar needs to be accessible by HTTP
|
||||||
|
//Otherwise, we skip the sidecar
|
||||||
|
if s.TTY != true {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(s.Ports) == 0 {
|
||||||
|
msg := fmt.Sprintf("sidecar %s must have at least one port.", s.Name)
|
||||||
|
return nil, util.NewUserError(codes.InvalidArgument, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Sidecars[si].MirrorVolumeMounts = ptr.Bool(true)
|
||||||
|
serviceNameUID := "s" + uuid.New().String() + "--" + namespace
|
||||||
|
serviceNameUIDDNSCompliant, err := uid2.GenerateUID(serviceNameUID, 63)
|
||||||
|
if err != nil {
|
||||||
|
return nil, util.NewUserError(codes.InvalidArgument, err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceName := serviceNameUIDDNSCompliant + "." + *c.systemConfig.Domain()
|
||||||
|
|
||||||
|
serviceTemplateName := uuid.New().String()
|
||||||
|
serviceTemplateNameAdd := "sys-k8s-service-template-add-" + serviceTemplateName
|
||||||
|
serviceTemplateNameDelete := "sys-k8s-service-template-delete-" + serviceTemplateName
|
||||||
|
serviceTaskName := "service-" + uuid.New().String()
|
||||||
|
serviceAddTaskName := "sys-add-" + serviceTaskName
|
||||||
|
serviceDeleteTaskName := "sys-delete-" + serviceTaskName
|
||||||
|
virtualServiceTemplateName := uuid.New().String()
|
||||||
|
virtualServiceTemplateNameAdd := "sys-k8s-virtual-service-template-add-" + virtualServiceTemplateName
|
||||||
|
virtualServiceTemplateNameDelete := "sys-k8s-virtual-service-template-delete-" + virtualServiceTemplateName
|
||||||
|
virtualServiceTaskName := "virtual-service-" + uuid.New().String()
|
||||||
|
virtualServiceAddTaskName := "sys-add-" + virtualServiceTaskName
|
||||||
|
virtualServiceDeleteTaskName := "sys-delete-" + virtualServiceTaskName
|
||||||
|
var servicePorts []corev1.ServicePort
|
||||||
|
var routes []*networking.HTTPRoute
|
||||||
|
for _, port := range s.Ports {
|
||||||
|
servicePort := corev1.ServicePort{
|
||||||
|
Name: port.Name,
|
||||||
|
Protocol: port.Protocol,
|
||||||
|
Port: port.ContainerPort,
|
||||||
|
TargetPort: intstr.FromInt(int(port.ContainerPort)),
|
||||||
|
}
|
||||||
|
servicePorts = append(servicePorts, servicePort)
|
||||||
|
route := networking.HTTPRoute{
|
||||||
|
Match: []*networking.HTTPMatchRequest{
|
||||||
|
{
|
||||||
|
Uri: &networking.StringMatch{
|
||||||
|
MatchType: &networking.StringMatch_Prefix{
|
||||||
|
Prefix: "/"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Route: []*networking.HTTPRouteDestination{
|
||||||
|
{
|
||||||
|
Destination: &networking.Destination{
|
||||||
|
Host: serviceNameUIDDNSCompliant,
|
||||||
|
Port: &networking.PortSelector{
|
||||||
|
Number: uint32(port.ContainerPort),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
routes = append(routes, &route)
|
||||||
|
}
|
||||||
|
service := corev1.Service{
|
||||||
|
TypeMeta: metav1.TypeMeta{
|
||||||
|
APIVersion: "v1",
|
||||||
|
Kind: "Service",
|
||||||
|
},
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: serviceNameUIDDNSCompliant,
|
||||||
|
},
|
||||||
|
Spec: corev1.ServiceSpec{
|
||||||
|
Ports: servicePorts,
|
||||||
|
Selector: map[string]string{
|
||||||
|
serviceTaskName: serviceNameUIDDNSCompliant,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
//Istio needs to know which pod to setup the route to
|
||||||
|
if wf.Spec.Templates[tIdx].Metadata.Labels == nil {
|
||||||
|
wf.Spec.Templates[tIdx].Metadata.Labels = make(map[string]string)
|
||||||
|
}
|
||||||
|
wf.Spec.Templates[tIdx].Metadata.Labels[serviceTaskName] = serviceNameUIDDNSCompliant
|
||||||
|
serviceManifestBytes, err := yaml2.Marshal(service)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
serviceManifest := string(serviceManifestBytes)
|
||||||
|
templateServiceResource := wfv1.Template{
|
||||||
|
Name: serviceTemplateNameAdd,
|
||||||
|
Metadata: wfv1.Metadata{
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"sidecar.istio.io/inject": "false",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Resource: &wfv1.ResourceTemplate{
|
||||||
|
Action: "create",
|
||||||
|
Manifest: serviceManifest,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
newTemplateOrder = append(newTemplateOrder, templateServiceResource)
|
||||||
|
//routes
|
||||||
|
virtualServiceNameUUID := "vs-" + uuid.New().String()
|
||||||
|
hosts := []string{serviceName}
|
||||||
|
wf.Spec.Templates[tIdx].Outputs.Parameters = append(wf.Spec.Templates[tIdx].Outputs.Parameters,
|
||||||
|
wfv1.Parameter{Name: "sys-sidecar-url--" + s.Name, Value: &serviceName},
|
||||||
|
)
|
||||||
|
virtualService := map[string]interface{}{
|
||||||
|
"apiVersion": "networking.istio.io/v1alpha3",
|
||||||
|
"kind": "VirtualService",
|
||||||
|
"metadata": metav1.ObjectMeta{
|
||||||
|
Name: virtualServiceNameUUID,
|
||||||
|
},
|
||||||
|
"spec": networking.VirtualService{
|
||||||
|
Http: routes,
|
||||||
|
Gateways: []string{"istio-system/ingressgateway"},
|
||||||
|
Hosts: hosts,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
virtualServiceManifestBytes, err := yaml2.Marshal(virtualService)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
virtualServiceManifest := string(virtualServiceManifestBytes)
|
||||||
|
|
||||||
|
templateRouteResource := wfv1.Template{
|
||||||
|
Name: virtualServiceTemplateNameAdd,
|
||||||
|
Metadata: wfv1.Metadata{
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"sidecar.istio.io/inject": "false",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Resource: &wfv1.ResourceTemplate{
|
||||||
|
Action: "create",
|
||||||
|
Manifest: virtualServiceManifest,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
newTemplateOrder = append(newTemplateOrder, templateRouteResource)
|
||||||
|
|
||||||
|
for i2, t2 := range wf.Spec.Templates {
|
||||||
|
if t2.Name == wf.Spec.Entrypoint {
|
||||||
|
if t2.DAG != nil {
|
||||||
|
tasks := wf.Spec.Templates[i2].DAG.Tasks
|
||||||
|
t := tasks[0]
|
||||||
|
sysDepFound := false
|
||||||
|
for _, d := range t.Dependencies {
|
||||||
|
if d == taskSysSendStatusName {
|
||||||
|
sysDepFound = true
|
||||||
|
wf.Spec.Templates[i2].DAG.Tasks[0].Dependencies =
|
||||||
|
[]string{virtualServiceAddTaskName}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sysDepFound == false {
|
||||||
|
wf.Spec.Templates[i2].DAG.Tasks[0].Dependencies = append(wf.Spec.Templates[i2].DAG.Tasks[0].Dependencies, virtualServiceAddTaskName)
|
||||||
|
}
|
||||||
|
|
||||||
|
wf.Spec.Templates[i2].DAG.Tasks = append(tasks, []wfv1.DAGTask{
|
||||||
|
{
|
||||||
|
Name: serviceAddTaskName,
|
||||||
|
Template: serviceTemplateNameAdd,
|
||||||
|
Dependencies: []string{taskSysSendStatusName},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: virtualServiceAddTaskName,
|
||||||
|
Template: virtualServiceTemplateNameAdd,
|
||||||
|
Dependencies: []string{serviceAddTaskName},
|
||||||
|
},
|
||||||
|
}...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Inject clean-up for service and virtualservice
|
||||||
|
templateServiceDeleteResource := wfv1.Template{
|
||||||
|
Name: serviceTemplateNameDelete,
|
||||||
|
Metadata: wfv1.Metadata{
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"sidecar.istio.io/inject": "false",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Resource: &wfv1.ResourceTemplate{
|
||||||
|
Action: "delete",
|
||||||
|
Manifest: serviceManifest,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
newTemplateOrder = append(newTemplateOrder, templateServiceDeleteResource)
|
||||||
|
|
||||||
|
templateRouteDeleteResource := wfv1.Template{
|
||||||
|
Name: virtualServiceTemplateNameDelete,
|
||||||
|
Metadata: wfv1.Metadata{
|
||||||
|
Annotations: map[string]string{
|
||||||
|
"sidecar.istio.io/inject": "false",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Resource: &wfv1.ResourceTemplate{
|
||||||
|
Action: "delete",
|
||||||
|
Manifest: virtualServiceManifest,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
newTemplateOrder = append(newTemplateOrder, templateRouteDeleteResource)
|
||||||
|
|
||||||
|
dagTasks := []wfv1.DAGTask{
|
||||||
|
{
|
||||||
|
Name: serviceDeleteTaskName,
|
||||||
|
Template: serviceTemplateNameDelete,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: virtualServiceDeleteTaskName,
|
||||||
|
Template: virtualServiceTemplateNameDelete,
|
||||||
|
Dependencies: []string{serviceDeleteTaskName},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if wf.Spec.OnExit != "" {
|
||||||
|
for _, t := range wf.Spec.Templates {
|
||||||
|
if t.Name == wf.Spec.OnExit {
|
||||||
|
t.DAG.Tasks = append(t.DAG.Tasks, dagTasks...)
|
||||||
|
sysExitDepFound := false
|
||||||
|
for dti, dt := range t.DAG.Tasks {
|
||||||
|
if dt.Name == taskSysSendExitStats {
|
||||||
|
sysExitDepFound = true
|
||||||
|
t.DAG.Tasks[dti].Dependencies = append(t.DAG.Tasks[dti].Dependencies, virtualServiceDeleteTaskName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sysExitDepFound == false {
|
||||||
|
t.DAG.Tasks[0].Dependencies = append(t.DAG.Tasks[0].Dependencies, virtualServiceDeleteTaskName)
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
exitHandlerDAG := wfv1.Template{
|
||||||
|
Name: "exit-handler",
|
||||||
|
DAG: &wfv1.DAGTemplate{
|
||||||
|
Tasks: dagTasks,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
wf.Spec.OnExit = "exit-handler"
|
||||||
|
wf.Spec.Templates = append(wf.Spec.Templates, exitHandlerDAG)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newTemplateOrder = append(newTemplateOrder, wf.Spec.Templates[tIdx])
|
||||||
|
|
||||||
|
}
|
||||||
|
return newTemplateOrder, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Client) ValidateWorkflowExecution(namespace string, manifest []byte) (err error) {
|
func (c *Client) ValidateWorkflowExecution(namespace string, manifest []byte) (err error) {
|
||||||
manifest, err = filterOutCustomTypesFromManifest(manifest)
|
manifest, err = filterOutCustomTypesFromManifest(manifest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -588,7 +860,7 @@ func (c *Client) createWorkflowExecutionDB(namespace string, workflowExecution *
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) FinishWorkflowExecutionStatisticViaExitHandler(namespace, name string, workflowTemplateID int64, phase wfv1.NodePhase, startedAt time.Time) (err error) {
|
func (c *Client) FinishWorkflowExecutionStatisticViaExitHandler(namespace, name string, phase wfv1.NodePhase, startedAt time.Time) (err error) {
|
||||||
_, err = sb.Update("workflow_executions").
|
_, err = sb.Update("workflow_executions").
|
||||||
SetMap(sq.Eq{
|
SetMap(sq.Eq{
|
||||||
"started_at": startedAt.UTC(),
|
"started_at": startedAt.UTC(),
|
||||||
@@ -597,7 +869,10 @@ func (c *Client) FinishWorkflowExecutionStatisticViaExitHandler(namespace, name
|
|||||||
"finished_at": time.Now().UTC(),
|
"finished_at": time.Now().UTC(),
|
||||||
"phase": phase,
|
"phase": phase,
|
||||||
}).
|
}).
|
||||||
Where(sq.Eq{"name": name}).
|
Where(sq.And{
|
||||||
|
sq.Eq{"name": name},
|
||||||
|
sq.NotEq{"phase": "Terminated"},
|
||||||
|
}).
|
||||||
RunWith(c.DB).
|
RunWith(c.DB).
|
||||||
Exec()
|
Exec()
|
||||||
|
|
||||||
@@ -1207,7 +1482,7 @@ func (c *Client) TerminateWorkflowExecution(namespace, uid string) (err error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = argoutil.TerminateWorkflow(c.ArgoprojV1alpha1().Workflows(namespace), uid)
|
err = argoutil.StopWorkflow(c.ArgoprojV1alpha1().Workflows(namespace), uid, "", "")
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -143,8 +143,7 @@ func (s *WorkflowServer) AddWorkflowExecutionStatistics(ctx context.Context, req
|
|||||||
return &empty.Empty{}, err
|
return &empty.Empty{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = client.FinishWorkflowExecutionStatisticViaExitHandler(req.Namespace, req.Uid,
|
err = client.FinishWorkflowExecutionStatisticViaExitHandler(req.Namespace, req.Uid, phase, workflow.Status.StartedAt.UTC())
|
||||||
req.Statistics.WorkflowTemplateId, phase, workflow.Status.StartedAt.UTC())
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &empty.Empty{}, err
|
return &empty.Empty{}, err
|
||||||
|
Reference in New Issue
Block a user