...

Source file src/edge-infra.dev/pkg/sds/ien/k8s/controllers/nodeagent/plugins/remoteagentconfig/remoteagentconfig.go

Documentation: edge-infra.dev/pkg/sds/ien/k8s/controllers/nodeagent/plugins/remoteagentconfig

     1  package remoteagentconfig
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"io/fs"
     9  	"sort"
    10  	"strconv"
    11  	"strings"
    12  	"text/template"
    13  	"time"
    14  
    15  	corev1 "k8s.io/api/core/v1"
    16  	ctrl "sigs.k8s.io/controller-runtime"
    17  	"sigs.k8s.io/controller-runtime/pkg/client"
    18  
    19  	"edge-infra.dev/pkg/edge/info"
    20  	"edge-infra.dev/pkg/sds/ien/k8s/controllers/nodeagent/config"
    21  	"edge-infra.dev/pkg/sds/lib/os/file"
    22  )
    23  
    24  var (
    25  	remoteAgentHostDataDir    = "/host-data/remote-access-agent"
    26  	remoteAgentSecretDir      = remoteAgentHostDataDir
    27  	remoteAgentDataDir        = "/data/remote-access-agent"
    28  	remoteAgentConfigFileName = "config.yaml"
    29  	remoteAgentSFileName      = "%d.adc.json"
    30  	configFileMode            = fs.FileMode(0644)
    31  	secretFileMode            = fs.FileMode(0644)
    32  
    33  	secretNamespace = "sds"
    34  	secretName      = "remote-agent-configuration"
    35  	configEntry     = "config.yaml.tpl"
    36  	adcEntry        = "key.json"
    37  )
    38  
    39  type Plugin struct{}
    40  
    41  type templateFields struct {
    42  	TerminalID      string
    43  	StoreID         string
    44  	BannerID        string
    45  	Provider        string
    46  	CredentialsPath string
    47  }
    48  
    49  func (remoteAccessAgentPlugin Plugin) Reconcile(ctx context.Context, object client.Object, conf config.Config) error {
    50  	secret, ok := object.(*corev1.Secret)
    51  	if !ok {
    52  		return nil
    53  	}
    54  
    55  	edgeInfo, err := conf.GetEdgeInfoConfig(ctx)
    56  	if err != nil {
    57  		return err
    58  	}
    59  
    60  	log := ctrl.LoggerFrom(ctx, "pluginName", "remoteagentconfig")
    61  	ctrl.LoggerInto(ctx, log)
    62  
    63  	// only reconcile the target secret
    64  	if !isTargetSecret(secret) {
    65  		return nil
    66  	}
    67  
    68  	terminalInfo, err := getTerminalInfo(ctx, conf)
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	// locate remote agent config dir
    74  	fileHandler := file.New()
    75  	return UpdateRemoteAgentConfig(ctx, secret, terminalInfo, edgeInfo, fileHandler)
    76  }
    77  
    78  func getTerminalInfo(ctx context.Context, conf config.Config) (map[string]string, error) {
    79  	ienode, err := conf.GetHostIENode(ctx)
    80  	if err != nil {
    81  		return nil, fmt.Errorf("locating IENode: %w", err)
    82  	}
    83  
    84  	return map[string]string{"terminalID": ienode.ObjectMeta.Labels["node.ncr.com/terminal-id"]}, nil
    85  }
    86  
    87  func isTargetSecret(secret *corev1.Secret) bool {
    88  	return secret.Name == secretName && secret.Namespace == secretNamespace
    89  }
    90  
    91  func UpdateRemoteAgentConfig(ctx context.Context, secret *corev1.Secret, terminalInfo map[string]string, edgeConf *info.EdgeInfo, fileHandler file.File) error {
    92  	newSecret, err := getSecretData(secret)
    93  	if err != nil {
    94  		return err
    95  	}
    96  
    97  	adcFiles, err := updateADCJSON(ctx, newSecret, fileHandler)
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	recentADCidx := len(adcFiles) - 1
   103  	latestADC := adcFiles[recentADCidx]
   104  
   105  	err = updateConfigyaml(ctx, secret, latestADC, terminalInfo, edgeConf, fileHandler)
   106  	if err != nil {
   107  		return err
   108  	}
   109  
   110  	// delete all but latest adc.json
   111  	for _, file := range adcFiles[:recentADCidx] {
   112  		err = fileHandler.Remove(remoteAgentSecretDir + "/" + file)
   113  		if err != nil {
   114  			return err
   115  		}
   116  	}
   117  
   118  	return nil
   119  }
   120  
   121  func updateConfigyaml(ctx context.Context, secret *corev1.Secret, adcFilepath string, terminalInfo map[string]string, edgeConf *info.EdgeInfo, fileHandler file.File) error {
   122  	conf, err := templateConfig(secret, adcFilepath, terminalInfo, edgeConf)
   123  	if err != nil {
   124  		return err
   125  	}
   126  
   127  	currentData, err := fileHandler.Read(remoteAgentHostDataDir + "/" + remoteAgentConfigFileName)
   128  	if err != nil {
   129  		if !errors.Is(err, fs.ErrNotExist) {
   130  			return err
   131  		}
   132  		currentData = []byte{}
   133  	}
   134  
   135  	if bytes.Equal(conf, currentData) {
   136  		return nil
   137  	}
   138  
   139  	ctrl.LoggerFrom(ctx).Info("Updating config.yaml data")
   140  	return fileHandler.SafeWrite(remoteAgentHostDataDir, remoteAgentConfigFileName, conf, configFileMode)
   141  }
   142  
   143  // Returns the decoded adc.json from the secret
   144  func getSecretData(secret *corev1.Secret) ([]byte, error) {
   145  	data, ok := secret.Data[adcEntry]
   146  	if !ok || data == nil {
   147  		return nil, fmt.Errorf("cannot find adc entry %s in secret %s", adcEntry, secret.Name)
   148  	}
   149  
   150  	return data, nil
   151  }
   152  
   153  // Returns a sorted list of all adc files in the config dir
   154  func sortedADCFiles(fileHandler file.File) ([]string, error) {
   155  	allFiles, err := fileHandler.ReadDir(remoteAgentSecretDir)
   156  	if err != nil {
   157  		return nil, fmt.Errorf("listing adc entries: %w", err)
   158  	}
   159  
   160  	// Find latest adc in dir
   161  	var adcFiles []struct {
   162  		timestamp int64
   163  		filename  string
   164  	}
   165  	// Filter the files by ones that follow the pattern `<timestamp>.adc.json`
   166  	for _, file := range allFiles {
   167  		if !strings.HasSuffix(file.Name(), ".adc.json") {
   168  			continue
   169  		}
   170  
   171  		parts := strings.Split(file.Name(), ".")
   172  		if len(parts) != 3 {
   173  			continue
   174  		}
   175  
   176  		timestamp, err := strconv.ParseInt(parts[0], 10, 64)
   177  		if err != nil {
   178  			continue
   179  		}
   180  
   181  		adcFiles = append(adcFiles, struct {
   182  			timestamp int64
   183  			filename  string
   184  		}{
   185  			timestamp: timestamp,
   186  			filename:  file.Name(),
   187  		})
   188  	}
   189  
   190  	// Sort by timestamp
   191  	sort.Slice(adcFiles, func(i, j int) bool { return adcFiles[i].timestamp < adcFiles[j].timestamp })
   192  
   193  	var adcFilenames []string
   194  	for _, file := range adcFiles {
   195  		adcFilenames = append(adcFilenames, file.filename)
   196  	}
   197  
   198  	return adcFilenames, nil
   199  }
   200  
   201  // Returns the data from the latest adc file on the disk. Returns an empty slice
   202  // if there is no file on disk
   203  func getCurrentADCData(adcFilenames []string, fileHandler file.File) ([]byte, error) {
   204  	recentIdx := len(adcFilenames) - 1
   205  
   206  	if recentIdx < 0 {
   207  		return []byte{}, nil
   208  	}
   209  
   210  	currentData, err := fileHandler.Read(remoteAgentSecretDir + "/" + adcFilenames[recentIdx])
   211  	if err != nil {
   212  		if !errors.Is(err, fs.ErrNotExist) {
   213  			return nil, fmt.Errorf("reading adc file: %w", err)
   214  		}
   215  		currentData = []byte{}
   216  	}
   217  
   218  	return currentData, nil
   219  }
   220  
   221  // Finds all existing secrets in the default configuration folder. Creates a new
   222  // secret with the given data when the latest secret does not match the given
   223  // data. Returns a slice of all secrets in the folder with the secret with the
   224  // given data at the end of the slice
   225  func updateADCJSON(ctx context.Context, secretData []byte, fileHandler file.File) ([]string, error) {
   226  	adcFilenames, err := sortedADCFiles(fileHandler)
   227  	if err != nil {
   228  		return nil, err
   229  	}
   230  
   231  	currentData, err := getCurrentADCData(adcFilenames, fileHandler)
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	if bytes.Equal(secretData, currentData) {
   237  		return adcFilenames, nil
   238  	}
   239  
   240  	newFilename := fmt.Sprintf(remoteAgentSFileName, time.Now().Unix())
   241  
   242  	ctrl.LoggerFrom(ctx).Info("Updating adc secret", "adcFile", newFilename)
   243  	err = fileHandler.SafeWrite(remoteAgentSecretDir, newFilename, secretData, secretFileMode)
   244  	if err != nil {
   245  		return nil, fmt.Errorf("writing adc file: %w", err)
   246  	}
   247  
   248  	return append(adcFilenames, newFilename), nil
   249  }
   250  
   251  func templateConfig(secret *corev1.Secret, adcFilepath string, terminalInfo map[string]string, edgeConf *info.EdgeInfo) ([]byte, error) {
   252  	var res bytes.Buffer
   253  	values := templateFields{
   254  		TerminalID:      terminalInfo["terminalID"],
   255  		StoreID:         edgeConf.ClusterEdgeID,
   256  		BannerID:        edgeConf.BannerEdgeID,
   257  		Provider:        edgeConf.ProjectID,
   258  		CredentialsPath: remoteAgentDataDir + "/" + adcFilepath,
   259  	}
   260  
   261  	data, ok := secret.Data[configEntry]
   262  	if !ok || data == nil {
   263  		return nil, fmt.Errorf("cannot find template %s in secret", configEntry)
   264  	}
   265  
   266  	template, err := template.New(remoteAgentConfigFileName + ".tpl").Parse(string(data))
   267  	if err != nil {
   268  		return nil, err
   269  	}
   270  
   271  	err = template.Execute(&res, values)
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  
   276  	return res.Bytes(), nil
   277  }
   278  

View as plain text