github.com/choria-io/go-choria@v0.28.1-0.20240416190746-b3bf9c7d5a45/providers/registration/inventory_content.go (about)

     1  // Copyright (c) 2021, R.I. Pienaar and the Choria Project contributors
     2  //
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package registration
     6  
     7  import (
     8  	"context"
     9  	"encoding/json"
    10  	"fmt"
    11  	"math/rand"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/choria-io/go-choria/internal/util"
    16  	"github.com/sirupsen/logrus"
    17  
    18  	"github.com/choria-io/go-choria/config"
    19  	"github.com/choria-io/go-choria/server/agents"
    20  	"github.com/choria-io/go-choria/server/data"
    21  	"github.com/choria-io/go-choria/statistics"
    22  )
    23  
    24  // InventoryContent is a fully managed registration plugin for the choria server instance
    25  // it reads the server inventory and publishing it to the collective regularly
    26  type InventoryContent struct {
    27  	c   *config.Config
    28  	log *logrus.Entry
    29  	si  ServerInfoSource
    30  }
    31  
    32  const inventoryContentProtocol = "choria:registration:inventorycontent:1"
    33  
    34  type InventoryData struct {
    35  	Agents      []agents.Metadata          `json:"agents"`
    36  	Classes     []string                   `json:"classes"`
    37  	Facts       json.RawMessage            `json:"facts"`
    38  	Status      *statistics.InstanceStatus `json:"status"`
    39  	Collectives []string                   `json:"collectives"`
    40  	BuildInfo   *InventoryBuildInfo        `json:"build_info"`
    41  	AutoAgents  []*InventoryMachineState   `json:"machines"`
    42  }
    43  
    44  type InventoryBuildInfo struct {
    45  	Version string `json:"version"`
    46  	SHA     string `json:"sha"`
    47  }
    48  
    49  type InventoryContentMessage struct {
    50  	Protocol string          `json:"protocol"`
    51  	Content  json.RawMessage `json:"content,omitempty"`
    52  	ZContent []byte          `json:"zcontent,omitempty"`
    53  }
    54  
    55  type InventoryMachineState struct {
    56  	Name    string `json:"name"`
    57  	Version string `json:"version"`
    58  	State   string `json:"state"`
    59  }
    60  
    61  // NewInventoryContent creates a new fully managed registration plugin instance
    62  func NewInventoryContent(c *config.Config, si ServerInfoSource, logger *logrus.Entry) (*InventoryContent, error) {
    63  	reg := &InventoryContent{si: si}
    64  
    65  	reg.Init(c, logger)
    66  
    67  	return reg, nil
    68  }
    69  
    70  // Init sets up the plugin
    71  func (ic *InventoryContent) Init(c *config.Config, logger *logrus.Entry) {
    72  	ic.c = c
    73  	ic.log = logger.WithFields(logrus.Fields{"registration": "inventory"})
    74  
    75  	ic.log.Infof("Configured Inventory Registration")
    76  }
    77  
    78  // StartRegistration starts stats a publishing loop
    79  func (ic *InventoryContent) StartRegistration(ctx context.Context, wg *sync.WaitGroup, interval int, output chan *data.RegistrationItem) {
    80  	defer wg.Done()
    81  
    82  	delay := time.Duration(rand.Intn(4)+1) * time.Second
    83  	ic.log.Infof("Sleeping %v before first registration publish", delay)
    84  	err := util.InterruptibleSleep(ctx, delay)
    85  	if err != nil {
    86  		return
    87  	}
    88  
    89  	err = ic.publish(output)
    90  	if err != nil {
    91  		ic.log.Errorf("Could not create registration data: %s", err)
    92  	}
    93  
    94  	ticker := time.NewTicker(time.Duration(interval) * time.Second)
    95  
    96  	for {
    97  		select {
    98  		case <-ticker.C:
    99  			err = ic.publish(output)
   100  			if err != nil {
   101  				ic.log.Errorf("Could not create registration data: %s", err)
   102  			}
   103  
   104  		case <-ctx.Done():
   105  			return
   106  		}
   107  	}
   108  }
   109  
   110  func (ic *InventoryContent) publish(output chan *data.RegistrationItem) error {
   111  	ic.log.Infof("Starting inventory registration poll")
   112  
   113  	bi := ic.si.BuildInfo()
   114  
   115  	idata := &InventoryData{
   116  		Classes:     ic.si.Classes(),
   117  		Facts:       ic.si.Facts(),
   118  		Collectives: ic.c.Collectives,
   119  		Status:      ic.si.Status(),
   120  		AutoAgents:  []*InventoryMachineState{},
   121  		BuildInfo: &InventoryBuildInfo{
   122  			Version: bi.Version(),
   123  			SHA:     bi.SHA(),
   124  		},
   125  	}
   126  
   127  	for _, a := range ic.si.KnownAgents() {
   128  		agent, ok := ic.si.AgentMetadata(a)
   129  		if ok {
   130  			idata.Agents = append(idata.Agents, agent)
   131  		}
   132  	}
   133  
   134  	ms, err := ic.si.MachinesStatus()
   135  	if err == nil {
   136  		for _, m := range ms {
   137  			idata.AutoAgents = append(idata.AutoAgents, &InventoryMachineState{
   138  				Name:    m.Name,
   139  				Version: m.Version,
   140  				State:   m.State,
   141  			})
   142  		}
   143  	}
   144  
   145  	msg := &InventoryContentMessage{Protocol: inventoryContentProtocol}
   146  
   147  	dat, err := json.Marshal(idata)
   148  	if err != nil {
   149  		return err
   150  	}
   151  
   152  	if ic.c.Choria.InventoryContentCompression {
   153  		zdat, err := compress(dat)
   154  		if err != nil {
   155  			ic.log.Warnf("Could not compress registration data: %s", err)
   156  		} else {
   157  			msg.ZContent = zdat
   158  		}
   159  	}
   160  
   161  	if msg.ZContent == nil {
   162  		msg.Content = dat
   163  	}
   164  
   165  	jdat, err := json.Marshal(msg)
   166  	if err != nil {
   167  		return fmt.Errorf("could not json marshal registration message: %s", err)
   168  	}
   169  
   170  	item := &data.RegistrationItem{
   171  		Data:        jdat,
   172  		Destination: ic.c.Choria.InventoryContentRegistrationTarget,
   173  	}
   174  
   175  	if item.Destination == "" {
   176  		item.TargetAgent = "registration"
   177  	}
   178  
   179  	output <- item
   180  
   181  	return nil
   182  }