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 }