github.com/IBM-Blockchain/fabric-operator@v1.0.4/pkg/offering/base/ca/override/deployment.go (about)

     1  /*
     2   * Copyright contributors to the Hyperledger Fabric Operator project
     3   *
     4   * SPDX-License-Identifier: Apache-2.0
     5   *
     6   * Licensed under the Apache License, Version 2.0 (the "License");
     7   * you may not use this file except in compliance with the License.
     8   * You may obtain a copy of the License at:
     9   *
    10   * 	  http://www.apache.org/licenses/LICENSE-2.0
    11   *
    12   * Unless required by applicable law or agreed to in writing, software
    13   * distributed under the License is distributed on an "AS IS" BASIS,
    14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    15   * See the License for the specific language governing permissions and
    16   * limitations under the License.
    17   */
    18  
    19  package override
    20  
    21  import (
    22  	"encoding/json"
    23  	"fmt"
    24  	"path/filepath"
    25  
    26  	"github.com/pkg/errors"
    27  
    28  	current "github.com/IBM-Blockchain/fabric-operator/api/v1beta1"
    29  	cav1 "github.com/IBM-Blockchain/fabric-operator/pkg/apis/ca/v1"
    30  	"github.com/IBM-Blockchain/fabric-operator/pkg/initializer/common/config"
    31  	"github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources"
    32  	"github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources/container"
    33  	"github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources/deployment"
    34  	dep "github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources/deployment"
    35  	"github.com/IBM-Blockchain/fabric-operator/pkg/manager/resources/serviceaccount"
    36  	"github.com/IBM-Blockchain/fabric-operator/pkg/util"
    37  
    38  	appsv1 "k8s.io/api/apps/v1"
    39  	corev1 "k8s.io/api/core/v1"
    40  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    41  )
    42  
    43  // Container names
    44  const (
    45  	INIT      = "init"
    46  	CA        = "ca"
    47  	HSMCLIENT = "hsm-client"
    48  )
    49  
    50  func (o *Override) Deployment(object v1.Object, deployment *appsv1.Deployment, action resources.Action) error {
    51  	instance := object.(*current.IBPCA)
    52  	switch action {
    53  	case resources.Create:
    54  		return o.CreateDeployment(instance, deployment)
    55  	case resources.Update:
    56  		return o.UpdateDeployment(instance, deployment)
    57  	}
    58  
    59  	return nil
    60  }
    61  
    62  func (o *Override) CreateDeployment(instance *current.IBPCA, k8sDep *appsv1.Deployment) error {
    63  	var err error
    64  
    65  	if !instance.Spec.License.Accept {
    66  		return errors.New("user must accept license before continuing")
    67  	}
    68  
    69  	deployment := dep.New(k8sDep)
    70  
    71  	name := instance.GetName()
    72  	deployment.Spec.Template.Spec.ServiceAccountName = serviceaccount.GetName(name)
    73  	err = o.CommonDeployment(instance, deployment)
    74  	if err != nil {
    75  		return err
    76  	}
    77  
    78  	caCont, err := deployment.GetContainer(CA)
    79  	if err != nil {
    80  		return errors.New("ca container not found in deployment spec")
    81  	}
    82  	initCont, err := deployment.GetContainer(INIT)
    83  	if err != nil {
    84  		return errors.New("init container not found in deployment spec")
    85  	}
    86  
    87  	deployment.SetImagePullSecrets(instance.Spec.ImagePullSecrets)
    88  
    89  	if !o.IsPostgres(instance) {
    90  		claimName := instance.Name + "-pvc"
    91  		if instance.Spec.CustomNames.PVC.CA != "" {
    92  			claimName = instance.Spec.CustomNames.PVC.CA
    93  		}
    94  		deployment.AppendPVCVolumeIfMissing("fabric-ca", claimName)
    95  
    96  		initCont.AppendVolumeMountWithSubPathIfMissing("fabric-ca", "/data", "fabric-ca-server")
    97  		caCont.AppendVolumeMountWithSubPathIfMissing("fabric-ca", "/data", "fabric-ca-server")
    98  	} else {
    99  		initCont.AppendVolumeMountIfMissing("shared", "/data")
   100  		caCont.AppendVolumeMountIfMissing("shared", "/data")
   101  	}
   102  
   103  	deployment.AppendSecretVolumeIfMissing("ca-crypto", instance.Name+"-ca-crypto")
   104  	deployment.AppendSecretVolumeIfMissing("tlsca-crypto", instance.Name+"-tlsca-crypto")
   105  	deployment.AppendConfigMapVolumeIfMissing("ca-config", instance.Name+"-ca-config")
   106  	deployment.AppendConfigMapVolumeIfMissing("tlsca-config", instance.Name+"-tlsca-config")
   107  	deployment.SetAffinity(o.GetAffinity(instance))
   108  
   109  	if instance.UsingHSMProxy() {
   110  		caCont.AppendEnvIfMissing("PKCS11_PROXY_SOCKET", instance.Spec.HSM.PKCS11Endpoint)
   111  	} else if instance.IsHSMEnabled() {
   112  		hsmConfig, err := config.ReadHSMConfig(o.Client, instance)
   113  		if err != nil {
   114  			return errors.Wrapf(err, "failed to apply hsm settings to '%s' deployment", instance.GetName())
   115  		}
   116  
   117  		hsmSettings(instance, hsmConfig, caCont, deployment)
   118  	}
   119  
   120  	return nil
   121  }
   122  
   123  func (o *Override) UpdateDeployment(instance *current.IBPCA, k8sDep *appsv1.Deployment) error {
   124  	deployment := dep.New(k8sDep)
   125  	err := o.CommonDeployment(instance, deployment)
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	if instance.UsingHSMProxy() {
   131  		caCont := deployment.MustGetContainer(CA)
   132  		caCont.UpdateEnv("PKCS11_PROXY_SOCKET", instance.Spec.HSM.PKCS11Endpoint)
   133  		deployment.UpdateContainer(caCont)
   134  	} else if instance.IsHSMEnabled() {
   135  		hsmInitCont := deployment.MustGetContainer(HSMCLIENT)
   136  		image := instance.Spec.Images
   137  		if image != nil {
   138  			hsmInitCont.SetImage(image.HSMImage, image.HSMTag)
   139  		}
   140  	}
   141  
   142  	return nil
   143  }
   144  
   145  func (o *Override) CommonDeployment(instance *current.IBPCA, deployment *dep.Deployment) error {
   146  	caCont := deployment.MustGetContainer(CA)
   147  	initCont := deployment.MustGetContainer(INIT)
   148  
   149  	if instance.Spec.CAResourcesSet() {
   150  		err := caCont.UpdateResources(instance.Spec.Resources.CA)
   151  		if err != nil {
   152  			return errors.Wrap(err, "update resources for ca failed")
   153  		}
   154  	}
   155  
   156  	if instance.Spec.InitResourcesSet() {
   157  		err := initCont.UpdateResources(instance.Spec.Resources.Init)
   158  		if err != nil {
   159  			return errors.Wrap(err, "update resources for init failed")
   160  		}
   161  	}
   162  
   163  	image := instance.Spec.Images
   164  	if image != nil {
   165  		caCont.SetImage(image.CAImage, image.CATag)
   166  		initCont.SetImage(image.CAInitImage, image.CAInitTag)
   167  	}
   168  
   169  	if o.IsPostgres(instance) {
   170  		deployment.SetStrategy(appsv1.RollingUpdateDeploymentStrategyType)
   171  	}
   172  
   173  	// TODO: Find a clean way to check for valid config other than the nested if/else statements
   174  	if instance.Spec.Replicas != nil {
   175  		if *instance.Spec.Replicas > 1 {
   176  			err := o.ValidateConfigOverride(instance.Spec.ConfigOverride)
   177  			if err != nil {
   178  				return err
   179  			}
   180  		}
   181  
   182  		deployment.SetReplicas(instance.Spec.Replicas)
   183  	}
   184  
   185  	return nil
   186  }
   187  
   188  func (o *Override) ValidateConfigOverride(configOverride *current.ConfigOverride) error {
   189  	var byteArray *[]byte
   190  	if configOverride == nil {
   191  		return errors.New("Failed to provide override configuration to support greater than 1 replicas")
   192  	}
   193  
   194  	if configOverride.CA != nil {
   195  		err := o.ValidateServerConfig(&configOverride.CA.Raw, "CA")
   196  		if err != nil {
   197  			return err
   198  		}
   199  	} else { // if it is nil call with empty bytearray
   200  		err := o.ValidateServerConfig(byteArray, "CA")
   201  		if err != nil {
   202  			return err
   203  		}
   204  	}
   205  
   206  	if configOverride.TLSCA != nil {
   207  		err := o.ValidateServerConfig(&configOverride.TLSCA.Raw, "TLSCA")
   208  		if err != nil {
   209  			return err
   210  		}
   211  	} else { // if it is nil call with empty bytearray
   212  		err := o.ValidateServerConfig(byteArray, "TLSCA")
   213  		if err != nil {
   214  			return err
   215  		}
   216  	}
   217  
   218  	return nil
   219  }
   220  
   221  func (o *Override) ValidateServerConfig(byteArray *[]byte, configType string) error {
   222  	if byteArray == nil {
   223  		return errors.New(fmt.Sprintf("Failed to provide database configuration for %s to support greater than 1 replicas", configType))
   224  	}
   225  
   226  	overrides := &cav1.ServerConfig{}
   227  	err := json.Unmarshal(*byteArray, overrides)
   228  	if err != nil {
   229  		return err
   230  	}
   231  
   232  	if overrides.DB != nil {
   233  		if overrides.DB.Type != "postgres" {
   234  			return errors.New(fmt.Sprintf("DB Type in %s config override should be `postgres` to allow replicas > 1", configType))
   235  		}
   236  
   237  		if overrides.DB.Datasource == "" {
   238  			return errors.New(fmt.Sprintf("Datasource in %s config override should not be empty to allow replicas > 1", configType))
   239  		}
   240  	}
   241  
   242  	return nil
   243  }
   244  
   245  func hsmInitContainer(instance *current.IBPCA, hsmConfig *config.HSMConfig) *container.Container {
   246  	hsmLibraryPath := hsmConfig.Library.FilePath
   247  	hsmLibraryName := filepath.Base(hsmLibraryPath)
   248  
   249  	f := false
   250  	user := int64(0)
   251  	mountPath := "/shared"
   252  	cont := &container.Container{
   253  		Container: &corev1.Container{
   254  			Name:            HSMCLIENT,
   255  			Image:           fmt.Sprintf("%s:%s", instance.Spec.Images.HSMImage, instance.Spec.Images.HSMTag),
   256  			ImagePullPolicy: corev1.PullAlways,
   257  			Command: []string{
   258  				"sh",
   259  				"-c",
   260  				fmt.Sprintf("mkdir -p %s/hsm && dst=\"%s/hsm/%s\" && echo \"Copying %s to ${dst}\" && mkdir -p $(dirname $dst) && cp -r %s $dst", mountPath, mountPath, hsmLibraryName, hsmLibraryPath, hsmLibraryPath),
   261  			},
   262  			SecurityContext: &corev1.SecurityContext{
   263  				RunAsUser:    &user,
   264  				RunAsNonRoot: &f,
   265  			},
   266  			VolumeMounts: []corev1.VolumeMount{
   267  				{
   268  					Name:      "shared",
   269  					MountPath: mountPath,
   270  				},
   271  			},
   272  			Resources: instance.GetResource("init"),
   273  		},
   274  	}
   275  
   276  	return cont
   277  }
   278  
   279  func hsmSettings(instance *current.IBPCA, hsmConfig *config.HSMConfig, caCont container.Container, deployment *deployment.Deployment) {
   280  	caCont.Command = []string{
   281  		"sh",
   282  		"-c",
   283  		"mkdir -p /data/tlsca && cp /config/tlsca/fabric-ca-server-config.yaml /data/tlsca && mkdir -p /data/ca && cp /config/ca/fabric-ca-server-config.yaml /data/ca && fabric-ca-server start --home /data/ca",
   284  	}
   285  
   286  	// Add volumes from HSM config to deployment container
   287  	for _, v := range hsmConfig.GetVolumes() {
   288  		deployment.AppendVolumeIfMissing(v)
   289  	}
   290  
   291  	// Add volume mounts from HSM config to CA container
   292  	for _, vm := range hsmConfig.GetVolumeMounts() {
   293  		caCont.AppendVolumeMountStructIfMissing(vm)
   294  	}
   295  
   296  	// Add environment variables from HSM config to CA container
   297  	for _, env := range hsmConfig.GetEnvs() {
   298  		caCont.AppendEnvStructIfMissing(env)
   299  	}
   300  
   301  	caCont.AppendVolumeMountWithSubPathIfMissing("shared", "/hsm/lib", "hsm")
   302  
   303  	// If a pull secret is required to pull HSM library image, update the deployment's image pull secrets
   304  	if hsmConfig.Library.Auth != nil {
   305  		deployment.Spec.Template.Spec.ImagePullSecrets = util.AppendPullSecretIfMissing(
   306  			deployment.Spec.Template.Spec.ImagePullSecrets,
   307  			hsmConfig.Library.Auth.ImagePullSecret,
   308  		)
   309  	}
   310  
   311  	// Add HSM init container to deployment, the init container is responsible for copying over HSM
   312  	// client library to the path expected by the CA
   313  	deployment.AddInitContainer(*hsmInitContainer(instance, hsmConfig))
   314  
   315  	// If daemon settings are configured in HSM config, create a sidecar that is running the daemon image
   316  	if hsmConfig.Daemon != nil {
   317  		hsmDaemonSettings(instance, hsmConfig, caCont, deployment)
   318  	}
   319  }
   320  
   321  func hsmDaemonSettings(instance *current.IBPCA, hsmConfig *config.HSMConfig, caCont container.Container, deployment *deployment.Deployment) {
   322  	// Unable to launch daemon if not running priviledged moe
   323  	t := true
   324  	caCont.SecurityContext.Privileged = &t
   325  	caCont.SecurityContext.AllowPrivilegeEscalation = &t
   326  
   327  	// Update command in deployment to ensure that deamon is running before starting the ca
   328  	caCont.Command = []string{
   329  		"sh",
   330  		"-c",
   331  		config.DAEMON_CHECK_CMD + " && mkdir -p /data/tlsca && cp /config/tlsca/fabric-ca-server-config.yaml /data/tlsca && mkdir -p /data/ca && cp /config/ca/fabric-ca-server-config.yaml /data/ca && fabric-ca-server start --home /data/ca",
   332  	}
   333  
   334  	// This is the shared volume where the file 'pkcsslotd-luanched' is touched to let
   335  	// other containers know that the daemon has successfully launched.
   336  	caCont.AppendVolumeMountIfMissing("shared", "/shared")
   337  
   338  	pvcVolumeName := "fabric-ca"
   339  	// Certain token information requires to be stored in persistent store, the administrator
   340  	// responsible for configuring HSM sets the HSM config to point to the path where the PVC
   341  	// needs to be mounted.
   342  	var pvcMount *corev1.VolumeMount
   343  	for _, vm := range hsmConfig.MountPaths {
   344  		if vm.UsePVC {
   345  			pvcMount = &corev1.VolumeMount{
   346  				Name:      pvcVolumeName,
   347  				MountPath: vm.MountPath,
   348  			}
   349  		}
   350  	}
   351  
   352  	// If a pull secret is required to pull daemon image, update the deployment's image pull secrets
   353  	if hsmConfig.Daemon.Auth != nil {
   354  		deployment.Spec.Template.Spec.ImagePullSecrets = util.AppendPullSecretIfMissing(
   355  			deployment.Spec.Template.Spec.ImagePullSecrets,
   356  			hsmConfig.Daemon.Auth.ImagePullSecret,
   357  		)
   358  	}
   359  
   360  	// Add daemon container to the deployment
   361  	config.AddDaemonContainer(hsmConfig, deployment, instance.GetResource(current.HSMDAEMON), pvcMount)
   362  
   363  	// If a pvc mount has been configured in HSM config, set the volume mount on the ca container
   364  	// and PVC volume to deployment if missing
   365  	if pvcMount != nil {
   366  		caCont.AppendVolumeMountStructIfMissing(*pvcMount)
   367  		deployment.AppendPVCVolumeIfMissing(pvcVolumeName, instance.PVCName())
   368  	}
   369  }