diff --git a/pkg/kube/activity.go b/pkg/kube/activity.go index cb13fba4c8..e398be77cb 100644 --- a/pkg/kube/activity.go +++ b/pkg/kube/activity.go @@ -1,20 +1,20 @@ package kube import ( + "encoding/json" "fmt" - "reflect" "strconv" "strings" "time" - "github.com/jenkins-x/jx/pkg/util" - - "github.com/ghodss/yaml" "github.com/jenkins-x/jx/pkg/apis/jenkins.io/v1" typev1 "github.com/jenkins-x/jx/pkg/client/clientset/versioned/typed/jenkins.io/v1" "github.com/jenkins-x/jx/pkg/gits" "github.com/jenkins-x/jx/pkg/log" + "github.com/jenkins-x/jx/pkg/util" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" ) type PipelineActivityKey struct { @@ -180,20 +180,19 @@ func (k *PipelineActivityKey) GetOrCreate(activities typev1.PipelineActivityInte } a, err := activities.Get(name, metav1.GetOptions{}) if err != nil { - create = true - a = defaultActivity + if errors.IsNotFound(err) { + create = true + a = defaultActivity + } else { + return defaultActivity, create, err + } } - oldSpec := a.Spec spec := &a.Spec updateActivitySpec(k, spec) if create { answer, err := activities.Create(a) return answer, true, err } else { - if !reflect.DeepEqual(&a.Spec, &oldSpec) { - answer, err := activities.Update(a) - return answer, false, err - } return a, false, nil } } @@ -407,6 +406,28 @@ func (k *PromoteStepActivityKey) GetOrCreatePromoteUpdate(activities typev1.Pipe return a, s, p, p.Update, created, err } +// PatchRow is a json patch structure +type PatchRow struct { + Op string `json:"op"` + Path string `json:"path"` + Value interface{} `json:"value"` +} + +// Almost making a full Update, but using Patch to work around problem with sometimes getting http status 409 back +func patchSpec(activities typev1.PipelineActivityInterface, activity *v1.PipelineActivity) (err error) { + things := make([]PatchRow, 1) + things[0].Value = activity.Spec + things[0].Op = "add" + things[0].Path = "/spec" + + patchBytes, err := json.Marshal(things) + if err != nil { + return + } + _, err = activities.Patch(activity.Name, types.JSONPatchType, patchBytes) + return +} + func (k *PromoteStepActivityKey) OnPromotePullRequest(activities typev1.PipelineActivityInterface, fn PromotePullRequestFn) error { if !k.IsValid() { return nil @@ -415,21 +436,16 @@ func (k *PromoteStepActivityKey) OnPromotePullRequest(activities typev1.Pipeline log.Warn("Warning: no PipelineActivities client available!") return nil } - a, s, ps, p, added, err := k.GetOrCreatePromotePullRequest(activities) + a, s, ps, p, _, err := k.GetOrCreatePromotePullRequest(activities) if err != nil { return err } - p1 := *p err = fn(a, s, ps, p) if err != nil { return err } - p2 := *p - if added || !reflect.DeepEqual(p1, p2) { - _, err = activities.Update(a) - } - return err + return patchSpec(activities, a) } func (k *PromoteStepActivityKey) OnPromoteUpdate(activities typev1.PipelineActivityInterface, fn PromoteUpdateFn) error { @@ -440,11 +456,10 @@ func (k *PromoteStepActivityKey) OnPromoteUpdate(activities typev1.PipelineActiv log.Warn("Warning: no PipelineActivities client available!") return nil } - a, s, ps, p, added, err := k.GetOrCreatePromoteUpdate(activities) + a, s, ps, p, _, err := k.GetOrCreatePromoteUpdate(activities) if err != nil { return err } - p1 := asYaml(a) if k.ApplicationURL != "" { ps.ApplicationURL = k.ApplicationURL } @@ -455,21 +470,7 @@ func (k *PromoteStepActivityKey) OnPromoteUpdate(activities typev1.PipelineActiv if k.ApplicationURL != "" { ps.ApplicationURL = k.ApplicationURL } - p2 := asYaml(a) - - if added || p1 == "" || p1 != p2 { - _, err = activities.Update(a) - } - return err -} - -func asYaml(activity *v1.PipelineActivity) string { - data, err := yaml.Marshal(activity) - if err == nil { - return string(data) - } - log.Warnf("Failed to marshal PipelineActivity to YAML %s: %s", activity.Name, err) - return "" + return patchSpec(activities, a) } func (k *PromoteStepActivityKey) matchesPreview(step *v1.PipelineActivityStep) bool { diff --git a/pkg/kube/activity_functions.go b/pkg/kube/activity_functions.go index 10ef3b67fc..8d475fd528 100644 --- a/pkg/kube/activity_functions.go +++ b/pkg/kube/activity_functions.go @@ -48,15 +48,9 @@ func StartPromotionPullRequest(a *v1.PipelineActivity, s *v1.PipelineActivitySte Time: time.Now(), } } - if a.Spec.WorkflowStatus != v1.ActivityStatusTypeRunning { - a.Spec.WorkflowStatus = v1.ActivityStatusTypeRunning - } - if a.Spec.Status != v1.ActivityStatusTypeRunning { - a.Spec.Status = v1.ActivityStatusTypeRunning - } - if p.Status != v1.ActivityStatusTypeRunning { - p.Status = v1.ActivityStatusTypeRunning - } + a.Spec.WorkflowStatus = v1.ActivityStatusTypeRunning + a.Spec.Status = v1.ActivityStatusTypeRunning + p.Status = v1.ActivityStatusTypeRunning return nil } @@ -71,15 +65,9 @@ func StartPromotionUpdate(a *v1.PipelineActivity, s *v1.PipelineActivityStep, ps Time: time.Now(), } } - if a.Spec.WorkflowStatus != v1.ActivityStatusTypeRunning { - a.Spec.WorkflowStatus = v1.ActivityStatusTypeRunning - } - if a.Spec.Status != v1.ActivityStatusTypeRunning { - a.Spec.Status = v1.ActivityStatusTypeRunning - } - if p.Status != v1.ActivityStatusTypeRunning { - p.Status = v1.ActivityStatusTypeRunning - } + a.Spec.WorkflowStatus = v1.ActivityStatusTypeRunning + a.Spec.Status = v1.ActivityStatusTypeRunning + p.Status = v1.ActivityStatusTypeRunning return nil } diff --git a/pkg/kube/activity_test.go b/pkg/kube/activity_test.go index 2f7b9b427b..5184685107 100644 --- a/pkg/kube/activity_test.go +++ b/pkg/kube/activity_test.go @@ -1,7 +1,10 @@ package kube_test import ( + "encoding/json" "fmt" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime/schema" "strconv" "testing" "time" @@ -44,7 +47,7 @@ func (m *MockPipelineActivityInterface) Get(name string, options metav1.GetOptio if ok { return a, nil } - return nil, fmt.Errorf("No such PipelineActivity %s", name) + return nil, errors.NewNotFound(schema.GroupResource{}, name) } func (m *MockPipelineActivityInterface) List(opts metav1.ListOptions) (*v1.PipelineActivityList, error) { @@ -61,7 +64,25 @@ func (m *MockPipelineActivityInterface) Watch(opts metav1.ListOptions) (watch.In return nil, fmt.Errorf("TODO") } +// SpecPatchRow is a json patch structure +type SpecPatchRow struct { + Op string `json:"op"` + Path string `json:"path"` + Value v1.PipelineActivitySpec `json:"value"` +} + func (m *MockPipelineActivityInterface) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1.PipelineActivity, err error) { + if pt == types.JSONPatchType { + patch := make([]SpecPatchRow, 1) + err := json.Unmarshal(data, &patch) + if err != nil { + return nil, err + } + if len(patch) == 1 && patch[0].Op == "add" && patch[0].Path == "/spec" { + m.Activities[name].Spec = patch[0].Value + return m.Activities[name], nil + } + } return nil, fmt.Errorf("TODO") }