[Kubernetes] external-dnsのPolicyの種類とそれぞれのPolicyに対応する変更ロジック

# 疑問１: external-dnsのPolicyの種類って何がある？

[https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/aws.md) `--policy=upsert-only` と書いてあるが、他にどんなpolicyがあるのか見当たらなかった。

```yaml
- --policy=upsert-only # would prevent ExternalDNS from deleting any records, omit to enable full synchronization
```

# policyの種類

- `sync`: Create, UpdateOld, UpdateNew, DeleteのすべてがActionに対応
- `upsert-only`: Create, UpdateOld, UpdateNewのみ対応
- `create-only`: Createのみ対応
    - [Create-only policy](https://github.com/kubernetes-sigs/external-dns/issues/1155)というissueからスタートして、　[このpr](https://github.com/kubernetes-sigs/external-dns/pull/1160)でマージされ、[v0.5.17](https://github.com/kubernetes-sigs/external-dns/releases/tag/v0.5.17)で追加された

# 疑問２: これらのpolicyがどうやって適応されてる？

# 内部ロジック読み解き

## 全体像

ロジック読み終わってから書いた図

![external-dns-policy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7059/7aa562bd-f226-8596-6377-1b35e1cf2c3f.png)


## 1. policyがcfg.policyの値によって設定されている (`main.go`)

[main.go#L337](https://github.com/kubernetes-sigs/external-dns/blob/12bba0b34098c2cd09a6ed3419d7732fcaa5d2d9/main.go#L337)

```go
	policy, exists := plan.Policies[cfg.Policy]
```

`plan.Policies` は?

### 1.1 `plan.Policies`はStringからPolicyへのmapである (`plan/policy.go`)

[plan/policy.go#L25-L29](https://github.com/kubernetes-sigs/external-dns/blob/12bba0b34098c2cd09a6ed3419d7732fcaa5d2d9/plan/policy.go#L25-L29)

```go:plan/policy.go
// Policies is a registry of available policies.
var Policies = map[string]Policy{
	"sync":        &SyncPolicy{},
	"upsert-only": &UpsertOnlyPolicy{},
	"create-only": &CreateOnlyPolicy{},
}
```

`sync`, `upsert-only`, `create-only`それぞれに対応するPolicyに変換

## 2. Policyを渡して、Controllerを初期化 (`main.go`)

[main.go#L342-L350](https://github.com/kubernetes-sigs/external-dns/blob/12bba0b34098c2cd09a6ed3419d7732fcaa5d2d9/main.go#L342-L350)

```go:main.go#L342-L350
	ctrl := controller.Controller{
		Source:               endpointsSource,
		Registry:             r,
		Policy:               policy,
		Interval:             cfg.Interval,
		DomainFilter:         domainFilter,
		ManagedRecordTypes:   cfg.ManagedDNSRecordTypes,
		MinEventSyncInterval: cfg.MinEventSyncInterval,
	}
```

## 3. MainはControllerのRunOnce()かRun()を呼ぶ

[main.go#L352-L370](https://github.com/kubernetes-sigs/external-dns/blob/12bba0b34098c2cd09a6ed3419d7732fcaa5d2d9/main.go#L352-L370)

```go:main.go#L352-L370
	if cfg.Once {
		err := ctrl.RunOnce(ctx)
		if err != nil {
			log.Fatal(err)
		}

		os.Exit(0)
	}

	if cfg.UpdateEvents {
		// Add RunOnce as the handler function that will be called when ingress/service sources have changed.
		// Note that k8s Informers will perform an initial list operation, which results in the handler
		// function initially being called for every Service/Ingress that exists
		ctrl.Source.AddEventHandler(ctx, func() { ctrl.ScheduleRunOnce(time.Now()) })
	}

	ctrl.ScheduleRunOnce(time.Now())
	ctrl.Run(ctx)
```

## 4. Controller内のRunもRunOnceを呼ぶ (`controller/controller.go`)

[controller.go#L187-L204](https://github.com/kubernetes-sigs/external-dns/blob/12bba0b34098c2cd09a6ed3419d7732fcaa5d2d9/controller/controller.go#L187-L204)

```go:controller.go#L187-L204
// Run runs RunOnce in a loop with a delay until context is canceled
func (c *Controller) Run(ctx context.Context) {
	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()
	for {
		if c.ShouldRunOnce(time.Now()) {
			if err := c.RunOnce(ctx); err != nil {
				log.Error(err)
			}
		}
		select {
		case <-ticker.C:
		case <-ctx.Done():
			log.Info("Terminating main controller loop")
			return
		}
	}
}
```

-> RunOnceにメインロジックがある

## 5. RunOnce()でPlanの初期化 (`controller/controller.go`)

[controller/controller.go#L148-L155](https://github.com/kubernetes-sigs/external-dns/blob/12bba0b34098c2cd09a6ed3419d7732fcaa5d2d9/controller/controller.go#L148-L155)

```go:controller.go#L148-L155
	plan := &plan.Plan{
		Policies:           []plan.Policy{c.Policy},
		Current:            records,
		Desired:            endpoints,
		DomainFilter:       c.DomainFilter,
		PropertyComparator: c.Registry.PropertyValuesEqual,
		ManagedRecords:     []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
	}
```

`Policies:           []plan.Policy{c.Policy}` でControllerにSetされたPolicyがセットされている。

## 6. `plan.Calculate()` が呼ばる (`controller/controller.go`)

[controller/controller.go#L157](https://github.com/kubernetes-sigs/external-dns/blob/12bba0b34098c2cd09a6ed3419d7732fcaa5d2d9/controller/controller.go#L157)

```go:controller.go#L157
	plan = plan.Calculate()
```

### 6.1 `plan.Calculate()`で changesの更新とPlanの初期化し、planを返す (`plan/plan.go`)

[plan/plan.go#L155-L166](https://github.com/kubernetes-sigs/external-dns/blob/12bba0b34098c2cd09a6ed3419d7732fcaa5d2d9/plan/plan.go#L155-L166)  で、

```go:plan.go#L155-L166
	for _, pol := range p.Policies {
		changes = pol.Apply(changes) // changesを更新
	}

	plan := &Plan{ // Planを初期化
		Current:        p.Current,
		Desired:        p.Desired,
		Changes:        changes,
		ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
	}

	return plan # 返す
```

`changes = pol.Apply(changes)` は?

### 6.2 `changes = pol.Apply(changes)` で Policyごとに、で対応するChangesを返す (`plan/policy.go`)

[plan/policy.go#L31-L59](https://github.com/kubernetes-sigs/external-dns/blob/12bba0b34098c2cd09a6ed3419d7732fcaa5d2d9/plan/policy.go#L31-L59) の`Apply` 関数がそれぞれのPolicyに定義されている

1. `SyncPolicy` なら `changes`をそのまま返す

    ```go
    // Apply applies the sync policy which returns the set of changes as is.
    func (p *SyncPolicy) Apply(changes *Changes) *Changes {
    	return changes
    }
    ```
2. `UpserOnlyPolicy` なら `Create, UpdateOld, UpdateNew` だけを返す

    ```go
    // Apply applies the upsert-only policy which strips out any deletions.
    func (p *UpsertOnlyPolicy) Apply(changes *Changes) *Changes {
        return &Changes{
            Create:    changes.Create,
            UpdateOld: changes.UpdateOld,
            UpdateNew: changes.UpdateNew,
        }
    }
    ```

3. `CreateOnlyPolicy` なら`Create` だけを返す

    ```go
    // Apply applies the create-only policy which strips out updates and deletions.
    func (p *CreateOnlyPolicy) Apply(changes *Changes) *Changes {
        return &Changes{
            Create: changes.Create,
        }
    }
    ```

## 7. 上のステップでセットされた`plan`を使って変更をApply (`controller/controller.go`)

[controller.go#L159](https://github.com/kubernetes-sigs/external-dns/blob/12bba0b34098c2cd09a6ed3419d7732fcaa5d2d9/controller/controller.go#L159)

```go:controller.go#L159
	err = c.Registry.ApplyChanges(ctx, plan.Changes)
```

Policyに対応するChangesで変更がApplyされている