You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
consul/internal/resource/demo/controller.go

187 lines
4.6 KiB

// Copyright (c) HashiCorp, Inc.
[COMPLIANCE] License changes (#18443) * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Adding explicit MPL license for sub-package This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository. * Updating the license from MPL to Business Source License Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at <Blog URL>, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl. * add missing license headers * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 * Update copyright file headers to BUSL-1.1 --------- Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
1 year ago
// SPDX-License-Identifier: BUSL-1.1
package demo
import (
"context"
"fmt"
"math/rand"
"github.com/oklog/ulid/v2"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
"github.com/hashicorp/consul/internal/controller"
"github.com/hashicorp/consul/internal/controller/dependency"
"github.com/hashicorp/consul/internal/resource"
"github.com/hashicorp/consul/proto-public/pbresource"
pbdemov2 "github.com/hashicorp/consul/proto/private/pbdemo/v2"
)
const statusKeyArtistController = "consul.io/artist-controller"
// RegisterControllers registers controllers for the demo types. Should only be
// called in dev mode.
func RegisterControllers(mgr *controller.Manager) {
mgr.Register(artistController())
}
func artistController() *controller.Controller {
return controller.NewController("artists", TypeV2Artist).
WithWatch(TypeV2Album, dependency.MapOwner).
WithReconciler(&artistReconciler{})
}
type artistReconciler struct{}
func (r *artistReconciler) Reconcile(ctx context.Context, rt controller.Runtime, req controller.Request) error {
rsp, err := rt.Client.Read(ctx, &pbresource.ReadRequest{Id: req.ID})
switch {
case status.Code(err) == codes.NotFound:
return nil
case err != nil:
return err
}
res := rsp.Resource
var artist pbdemov2.Artist
if err := res.Data.UnmarshalTo(&artist); err != nil {
return err
}
conditions := []*pbresource.Condition{
{
Type: "Accepted",
State: pbresource.Condition_STATE_TRUE,
Reason: "Accepted",
Message: fmt.Sprintf("Artist '%s' accepted", artist.Name),
},
}
numAlbums := 3
if artist.Genre == pbdemov2.Genre_GENRE_BLUES {
numAlbums = 10
}
desiredAlbums, err := generateV2AlbumsDeterministic(res.Id, numAlbums)
if err != nil {
return err
}
actualAlbums, err := rt.Client.List(ctx, &pbresource.ListRequest{
Type: TypeV2Album,
Tenancy: res.Id.Tenancy,
NamePrefix: fmt.Sprintf("%s-", res.Id.Name),
})
if err != nil {
return err
}
writes, deletions, err := diffAlbums(desiredAlbums, actualAlbums.Resources)
if err != nil {
return err
}
for _, w := range writes {
if _, err := rt.Client.Write(ctx, &pbresource.WriteRequest{Resource: w}); err != nil {
return err
}
}
for _, d := range deletions {
if _, err := rt.Client.Delete(ctx, &pbresource.DeleteRequest{Id: d}); err != nil {
return err
}
}
for _, want := range desiredAlbums {
var album pbdemov2.Album
if err := want.Data.UnmarshalTo(&album); err != nil {
return err
}
conditions = append(conditions, &pbresource.Condition{
Type: "AlbumCreated",
State: pbresource.Condition_STATE_TRUE,
Reason: "AlbumCreated",
Message: fmt.Sprintf("Album '%s' created for artist '%s'", album.Title, artist.Name),
Resource: resource.Reference(want.Id, ""),
})
}
newStatus := &pbresource.Status{
ObservedGeneration: res.Generation,
Conditions: conditions,
}
if resource.EqualStatus(res.Status[statusKeyArtistController], newStatus, false) {
return nil
}
_, err = rt.Client.WriteStatus(ctx, &pbresource.WriteStatusRequest{
Id: res.Id,
Key: statusKeyArtistController,
Status: newStatus,
})
return err
}
func diffAlbums(want, have []*pbresource.Resource) ([]*pbresource.Resource, []*pbresource.ID, error) {
haveMap := make(map[string]*pbresource.Resource, len(have))
for _, r := range have {
haveMap[r.Id.Name] = r
}
wantMap := make(map[string]struct{}, len(want))
for _, r := range want {
wantMap[r.Id.Name] = struct{}{}
}
writes := make([]*pbresource.Resource, 0)
for _, w := range want {
h, ok := haveMap[w.Id.Name]
if ok {
var wd, hd pbdemov2.Album
if err := w.Data.UnmarshalTo(&wd); err != nil {
return nil, nil, err
}
if err := h.Data.UnmarshalTo(&hd); err != nil {
return nil, nil, err
}
if proto.Equal(&wd, &hd) {
continue
}
}
writes = append(writes, w)
}
deletions := make([]*pbresource.ID, 0)
for _, h := range have {
if _, ok := wantMap[h.Id.Name]; ok {
continue
}
deletions = append(deletions, h.Id)
}
return writes, deletions, nil
}
func generateV2AlbumsDeterministic(artistID *pbresource.ID, count int) ([]*pbresource.Resource, error) {
uid, err := ulid.Parse(artistID.Uid)
if err != nil {
return nil, fmt.Errorf("failed to parse Uid: %w", err)
}
rand := rand.New(rand.NewSource(int64(uid.Time())))
albums := make([]*pbresource.Resource, count)
for i := 0; i < count; i++ {
album, err := generateV2Album(artistID, rand)
if err != nil {
return nil, err
}
// Add suffix to avoid collisions.
album.Id.Name = fmt.Sprintf("%s-%d", album.Id.Name, i)
albums[i] = album
}
return albums, nil
}