github.com/sealerio/sealer@v0.11.1-0.20240507115618-f4f89c5853ae/pkg/registry/installer.go (about)

     1  // Copyright © 2022 Alibaba Group Holding Ltd.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package registry
    16  
    17  import (
    18  	"fmt"
    19  	"net"
    20  	"path/filepath"
    21  	"strconv"
    22  
    23  	"github.com/sirupsen/logrus"
    24  
    25  	"github.com/sealerio/sealer/common"
    26  	"github.com/sealerio/sealer/pkg/clustercert/cert"
    27  	"github.com/sealerio/sealer/pkg/imagedistributor"
    28  	"github.com/sealerio/sealer/pkg/infradriver"
    29  	v2 "github.com/sealerio/sealer/types/api/v2"
    30  	netutils "github.com/sealerio/sealer/utils/net"
    31  	osutils "github.com/sealerio/sealer/utils/os"
    32  	strutils "github.com/sealerio/sealer/utils/strings"
    33  )
    34  
    35  // Installer provide registry lifecycle management.
    36  type Installer interface {
    37  	// Reconcile registry deploy hosts thought comparing current deploy host and desiredHosts and return the final registry deploy hosts.
    38  	// if current deploy host is less than desiredHosts , means scale-up registry node
    39  	// if current deploy host is bigger than desiredHosts , means scale-down registry node
    40  	// if current deploy host is equal targetHosts , do nothing
    41  	// launch registry node
    42  	// scale-up registry node
    43  	// scale down registry node
    44  	Reconcile(desiredHosts []net.IP) ([]net.IP, error)
    45  
    46  	// Clean all registry deploy hosts
    47  	Clean() error
    48  }
    49  
    50  func NewInstaller(currentDeployHost []net.IP,
    51  	regConfig *v2.LocalRegistry,
    52  	infraDriver infradriver.InfraDriver,
    53  	distributor imagedistributor.Distributor) Installer {
    54  	return &localInstaller{
    55  		currentDeployHosts: currentDeployHost,
    56  		infraDriver:        infraDriver,
    57  		LocalRegistry:      regConfig,
    58  		distributor:        distributor,
    59  	}
    60  }
    61  
    62  type localInstaller struct {
    63  	*v2.LocalRegistry
    64  	currentDeployHosts []net.IP
    65  	infraDriver        infradriver.InfraDriver
    66  	distributor        imagedistributor.Distributor
    67  }
    68  
    69  func (l *localInstaller) Reconcile(desiredHosts []net.IP) ([]net.IP, error) {
    70  	// if deployHosts is null,means first time installation
    71  	if len(l.currentDeployHosts) == 0 {
    72  		err := l.install(desiredHosts)
    73  		if err != nil {
    74  			return nil, err
    75  		}
    76  		return desiredHosts, nil
    77  	}
    78  
    79  	joinedHosts, deletedHosts := strutils.Diff(l.currentDeployHosts, desiredHosts)
    80  	// if targetHosts is equal deployHosts, just return.
    81  	if len(joinedHosts) == 0 && len(deletedHosts) == 0 {
    82  		return l.currentDeployHosts, nil
    83  	}
    84  
    85  	// join new hosts
    86  	if len(joinedHosts) != 0 {
    87  		err := l.install(joinedHosts)
    88  		if err != nil {
    89  			return nil, err
    90  		}
    91  		return append(l.currentDeployHosts, joinedHosts...), nil
    92  	}
    93  
    94  	// delete hosts
    95  	if len(deletedHosts) != 0 {
    96  		err := l.clean(deletedHosts)
    97  		if err != nil {
    98  			return nil, err
    99  		}
   100  		return netutils.RemoveIPs(l.currentDeployHosts, deletedHosts), nil
   101  	}
   102  
   103  	return nil, nil
   104  }
   105  
   106  func (l *localInstaller) install(deployHosts []net.IP) error {
   107  	logrus.Infof("will launch local private registry on %+v\n", deployHosts)
   108  
   109  	if err := l.syncBasicAuthFile(deployHosts); err != nil {
   110  		return err
   111  	}
   112  
   113  	if err := l.syncRegistryCert(deployHosts); err != nil {
   114  		return err
   115  	}
   116  
   117  	if err := l.reconcileRegistry(deployHosts); err != nil {
   118  		return err
   119  	}
   120  	return nil
   121  }
   122  
   123  func (l *localInstaller) syncBasicAuthFile(hosts []net.IP) error {
   124  	//gen basic auth info: if not config, will skip.
   125  	if l.RegistryConfig.Username == "" || l.RegistryConfig.Password == "" {
   126  		return nil
   127  	}
   128  
   129  	var (
   130  		basicAuthFile = filepath.Join(l.infraDriver.GetClusterRootfsPath(), "etc", common.DefaultRegistryHtPasswdFile)
   131  	)
   132  
   133  	if !osutils.IsFileExist(basicAuthFile) {
   134  		htpasswd, err := GenerateHTTPBasicAuth(l.RegistryConfig.Username, l.RegistryConfig.Password)
   135  		if err != nil {
   136  			return err
   137  		}
   138  
   139  		err = osutils.NewCommonWriter(basicAuthFile).WriteFile([]byte(htpasswd))
   140  		if err != nil {
   141  			return err
   142  		}
   143  	}
   144  
   145  	for _, deployHost := range hosts {
   146  		err := l.infraDriver.Copy(deployHost, basicAuthFile, basicAuthFile)
   147  		if err != nil {
   148  			return fmt.Errorf("failed to copy registry auth file to %s: %v", basicAuthFile, err)
   149  		}
   150  	}
   151  
   152  	return nil
   153  }
   154  
   155  func (l *localInstaller) syncRegistryCert(hosts []net.IP) error {
   156  	// if deploy registry as InsecureMode ,skip to gen cert.
   157  	if *l.Insecure {
   158  		return nil
   159  	}
   160  	var (
   161  		certPath     = filepath.Join(l.infraDriver.GetClusterRootfsPath(), "certs")
   162  		certName     = l.Domain
   163  		fullCertName = certName + ".crt"
   164  		fullKeyName  = certName + ".key"
   165  	)
   166  
   167  	certExisted := osutils.IsFileExist(filepath.Join(certPath, fullCertName))
   168  	keyExisted := osutils.IsFileExist(filepath.Join(certPath, fullKeyName))
   169  
   170  	if certExisted && !keyExisted || !certExisted && keyExisted {
   171  		return fmt.Errorf("failed to sync registry cert file %s or %s is not existed", fullCertName, fullKeyName)
   172  	}
   173  
   174  	if !certExisted && !keyExisted {
   175  		if err := l.gen(certPath, certName); err != nil {
   176  			return err
   177  		}
   178  	}
   179  
   180  	for _, deployHost := range hosts {
   181  		err := l.infraDriver.Copy(deployHost, certPath, certPath)
   182  		if err != nil {
   183  			return fmt.Errorf("failed to copy registry cert to deployHost: %v", err)
   184  		}
   185  	}
   186  
   187  	return nil
   188  }
   189  
   190  func (l *localInstaller) gen(certPath, certName string) error {
   191  	DNSNames := []string{l.Domain}
   192  	if l.Cert.SubjectAltName != nil {
   193  		DNSNames = append(DNSNames, l.Cert.SubjectAltName.IPs...)
   194  		DNSNames = append(DNSNames, l.Cert.SubjectAltName.DNSNames...)
   195  	}
   196  
   197  	regCertConfig := cert.CertificateDescriptor{
   198  		CommonName:   "registry-ca",
   199  		DNSNames:     DNSNames,
   200  		Organization: nil,
   201  		Year:         100,
   202  		AltNames:     cert.AltNames{},
   203  		Usages:       nil,
   204  	}
   205  
   206  	caGenerator := cert.NewAuthorityCertificateGenerator(regCertConfig)
   207  	caCert, caKey, err := caGenerator.Generate()
   208  	if err != nil {
   209  		return fmt.Errorf("unable to generate registry cert: %v", err)
   210  	}
   211  
   212  	// write cert file to disk
   213  	err = cert.NewCertificateFileManger(certPath, certName).Write(caCert, caKey)
   214  	if err != nil {
   215  		return fmt.Errorf("unable to save registry cert: %v", err)
   216  	}
   217  
   218  	return nil
   219  }
   220  
   221  func (l *localInstaller) reconcileRegistry(hosts []net.IP) error {
   222  	var (
   223  		rootfs  = l.infraDriver.GetClusterRootfsPath()
   224  		dataDir = filepath.Join(rootfs, "registry")
   225  	)
   226  	// distribute registry data
   227  	if err := l.distributor.DistributeRegistry(hosts, dataDir); err != nil {
   228  		return err
   229  	}
   230  
   231  	// bash init-registry.sh ${port} ${mountData} ${domain}
   232  	clusterEnvs := l.infraDriver.GetClusterEnv()
   233  	initRegistry := fmt.Sprintf("cd %s/scripts && bash init-registry.sh %s %s %s", rootfs, strconv.Itoa(l.Port), dataDir, l.Domain)
   234  	for _, deployHost := range hosts {
   235  		if err := l.infraDriver.CmdAsync(deployHost, clusterEnvs, initRegistry); err != nil {
   236  			return err
   237  		}
   238  	}
   239  	return nil
   240  }
   241  
   242  func (l *localInstaller) clean(cleanHosts []net.IP) error {
   243  	deleteRegistryCommand := "if docker inspect %s 2>/dev/null;then docker rm -f %[1]s;fi && ((! nerdctl ps -a 2>/dev/null |grep %[1]s) || (nerdctl stop %[1]s && nerdctl rmi -f %[1]s))"
   244  	for _, deployHost := range cleanHosts {
   245  		if err := l.infraDriver.CmdAsync(deployHost, nil, fmt.Sprintf(deleteRegistryCommand, "sealer-registry")); err != nil {
   246  			return err
   247  		}
   248  	}
   249  	return nil
   250  }
   251  
   252  func (l *localInstaller) Clean() error {
   253  	return l.clean(l.currentDeployHosts)
   254  }