pkg/cloud/rgraph/rnode/forwardingrule/node.go (151 lines of code) (raw):
/*
Copyright 2023 Google LLC
Licensed 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
https://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 forwardingrule
import (
"fmt"
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud"
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/api"
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/rgraph/exec"
"github.com/GoogleCloudPlatform/k8s-cloud-provider/pkg/cloud/rgraph/rnode"
alpha "google.golang.org/api/compute/v0.alpha"
beta "google.golang.org/api/compute/v0.beta"
"google.golang.org/api/compute/v1"
)
func nodeErr(s string, args ...any) error { return fmt.Errorf("forwardingRule: "+s, args...) }
type forwardingRuleNode struct {
rnode.NodeBase
resource ForwardingRule
}
var _ rnode.Node = (*forwardingRuleNode)(nil)
func (n *forwardingRuleNode) Resource() rnode.UntypedResource { return n.resource }
// changedFields is a helper that interprets the set of fields that have been changed in a Diff.
type changedFields struct {
target bool
labels bool
other bool
// messages are human-readable descriptions of the changed fields.
messages []string
}
// process an item from the diff. returns true if the item can be handled
// without recreating the resource.
func (c *changedFields) process(item api.DiffItem) bool {
var messages []string
switch {
case api.Path{}.Pointer().Field("Target").Equal(item.Path):
c.messages = append(messages, fmt.Sprintf("Target (%q -> %q)", item.A, item.B))
c.target = true
return true
case item.Path.HasPrefix(api.Path{}.Pointer().Field("Labels")):
c.messages = append(messages, fmt.Sprintf("Labels (%v -> %v)", item.A, item.B))
c.labels = true
return true
default:
c.messages = append(messages, fmt.Sprintf("%s (%v -> %v)", item.Path, item.A, item.B))
c.other = true
}
return false
}
func (n *forwardingRuleNode) Diff(gotNode rnode.Node) (*rnode.PlanDetails, error) {
got, ok := gotNode.(*forwardingRuleNode)
if !ok {
return nil, nodeErr("invalid type to Diff: %T", gotNode)
}
diff, err := got.resource.Diff(n.resource)
if err != nil {
return nil, nodeErr("Diff: %w", err)
}
if diff.HasDiff() {
var changed changedFields
for _, item := range diff.Items {
changed.process(item)
}
if !changed.other {
return &rnode.PlanDetails{
Operation: rnode.OpUpdate,
Why: fmt.Sprintf("update in place (changed=%+v)", changed),
Diff: diff,
}, nil
}
return &rnode.PlanDetails{
Operation: rnode.OpRecreate,
Why: "needs to be recreated",
Diff: diff,
}, nil
}
return &rnode.PlanDetails{
Operation: rnode.OpNothing,
Why: "No diff between got and want",
}, nil
}
func (n *forwardingRuleNode) Actions(got rnode.Node) ([]exec.Action, error) {
op := n.Plan().Op()
switch op {
case rnode.OpCreate:
return n.createActions()
case rnode.OpDelete:
return rnode.DeleteActions[compute.ForwardingRule, alpha.ForwardingRule, beta.ForwardingRule](&ops{}, got, n)
case rnode.OpNothing:
return []exec.Action{exec.NewExistsAction(n.ID())}, nil
case rnode.OpRecreate:
return rnode.RecreateActions[compute.ForwardingRule, alpha.ForwardingRule, beta.ForwardingRule](&ops{}, got, n, n.resource)
case rnode.OpUpdate:
return n.updateActions(got)
}
return nil, nodeErr("invalid plan op %s", op)
}
func (n *forwardingRuleNode) Builder() rnode.Builder {
b := &builder{}
b.Init(n.ID(), n.State(), n.Ownership(), n.resource)
return b
}
func (n *forwardingRuleNode) createActions() ([]exec.Action, error) {
want, err := rnode.CreatePreconditions(n)
if err != nil {
return nil, err
}
return []exec.Action{
newForwardingRuleCreateAction(n.ID(), n.resource, want),
}, nil
}
func (n *forwardingRuleNode) updateActions(ngot rnode.Node) ([]exec.Action, error) {
details := n.Plan().Details()
if details == nil {
return nil, nodeErr("updateActions: node %s has not been planned", n.ID())
}
got, ok := ngot.(*forwardingRuleNode)
if !ok {
return nil, nodeErr("updateActions: node %s has invalid type %T", n.ID(), ngot)
}
act := &forwardingRuleUpdateAction{id: n.ID()}
var changed changedFields
for _, item := range details.Diff.Items {
if !changed.process(item) {
return nil, nodeErr("updateActions %s: field %s cannot be updated in place", n.ID(), item.Path)
}
}
if changed.target {
oldTarget, err := parseTarget(fmt.Sprintf("updateActions %s", n.ID()), got)
if err != nil {
return nil, err
}
target, err := parseTarget(fmt.Sprintf("updateActions %s", n.ID()), n)
if err != nil {
return nil, err
}
act.Want = append(act.Want, exec.NewExistsEvent(target))
act.oldTarget = oldTarget
act.target = target
}
if changed.labels {
gotRes, _ := got.resource.ToGA()
wantRes, _ := n.resource.ToGA()
act.labelFingerprint = gotRes.LabelFingerprint
act.labels = wantRes.Labels
}
return []exec.Action{
// Action: Signal resource exists.
exec.NewExistsAction(n.ID()),
// Action: Do the updates.
act,
}, nil
}
func parseTarget(errPrefix string, n *forwardingRuleNode) (*cloud.ResourceID, error) {
res, _ := n.resource.ToGA()
ret, err := cloud.ParseResourceURL(res.Target)
if err != nil {
return nil, nodeErr("%s: invalid .Target %q: %w", errPrefix, res.Target, err)
}
return ret, nil
}