cloud/digitalocean/droplet/resources/firewall.go
func (r *Firewall) Actual(immutable *cluster.Cluster) (*cluster.Cluster, cloud.Resource, error) {
	logger.Debug("firewall.Actual")

	newResource := defaultFirewallStruct()
	// Digital Firewalls.Get requires firewall ID, which we will not always have.thats why using List.
	firewalls, _, err := Sdk.Client.Firewalls.List(context.TODO(), &godo.ListOptions{})
	if err != nil {
		return nil, nil, fmt.Errorf("failed to get firwalls info")
	}
	for _, firewall := range firewalls {
		if firewall.Name == r.Name { // In digitalOcean Firwall names are unique.
			// gotcha get all details from this firewall and populate actual.
			firewallBytes, err := json.Marshal(firewall)
			if err != nil {
				return nil, nil, fmt.Errorf("failed to marshal DO firewall details err: %v", err)
			}
			if err := json.Unmarshal(firewallBytes, newResource); err != nil {
				return nil, nil, fmt.Errorf("failed to unmarhal DO firewall details err: %v", err)
			}
			// hack: DO api doesn't take "0" as portRange, but returns "0" for port range in firewall.List.
			for i := 0; i < len(newResource.OutboundRules); i++ {
				if newResource.OutboundRules[i].PortRange == "0" {
					newResource.OutboundRules[i].PortRange = "all"
				}
			}
			for i := 0; i < len(newResource.InboundRules); i++ {
				if newResource.InboundRules[i].PortRange == "0" {
					newResource.InboundRules[i].PortRange = "all"
				}
			}
		}
	}

	newCluster := r.immutableRender(newResource, immutable)
	return newCluster, newResource, nil
}
func (r *Firewall) Actual(immutable *cluster.Cluster) (*cluster.Cluster, cloud.Resource, error) {
	logger.Debug("firewall.Actual")

	newResource := defaultFirewallStruct()
	// Digital Firewalls.Get requires firewall ID, which we will not always have.thats why using List.
	firewalls, _, err := Sdk.Client.Firewalls.List(context.TODO(), &godo.ListOptions{})
	if err != nil {
		return nil, nil, fmt.Errorf("failed to get firwalls info")
	}
	for _, firewall := range firewalls {
		if firewall.Name == r.Name { // In digitalOcean Firwall names are unique.
			// gotcha get all details from this firewall and populate actual.
			firewallBytes, err := json.Marshal(firewall)
			if err != nil {
				return nil, nil, fmt.Errorf("failed to marshal DO firewall details err: %v", err)
			}
			if err := json.Unmarshal(firewallBytes, newResource); err != nil {
				return nil, nil, fmt.Errorf("failed to unmarhal DO firewall details err: %v", err)
			}
			// hack: DO api doesn't take "0" as portRange, but returns "0" for port range in firewall.List.
			for i := 0; i < len(newResource.OutboundRules); i++ {
				if newResource.OutboundRules[i].PortRange == "0" {
					newResource.OutboundRules[i].PortRange = "all"
				}
			}
			for i := 0; i < len(newResource.InboundRules); i++ {
				if newResource.InboundRules[i].PortRange == "0" {
					newResource.InboundRules[i].PortRange = "all"
				}
			}
		}
	}

	newCluster := r.immutableRender(newResource, immutable)
	return newCluster, newResource, nil
}
// Actual calls DO firewall Api and returns the actual state of firewall in the cloud.