Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ability to use bootstrap tokens from image secrets #932

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api/v1alpha1/humiobootstraptoken_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ type HumioBootstrapTokenStatus struct {
// HashedTokenSecret is the secret reference that contains the hashed token to use for this HumioBootstrapToken. This is set regardless of whether it's defined
// in the spec or automatically created
HashedTokenSecretKeyRef HumioHashedTokenSecretStatus `json:"hashedTokenSecretStatus,omitempty"`
// BootstrapImage is the image that was used to issue the token
BootstrapImage string `json:"bootstrapImage,omitempty"`
}

// HumioTokenSecretStatus contains the secret key reference to a kubernetes secret containing the bootstrap token secret. This is set regardless of whether it's defined
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,10 @@ spec:
status:
description: HumioBootstrapTokenStatus defines the observed state of HumioBootstrapToken.
properties:
bootstrapImage:
description: BootstrapImage is the image that was used to issue the
token
type: string
hashedTokenSecretStatus:
description: |-
HashedTokenSecret is the secret reference that contains the hashed token to use for this HumioBootstrapToken. This is set regardless of whether it's defined
Expand Down
4 changes: 4 additions & 0 deletions config/crd/bases/core.humio.com_humiobootstraptokens.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,10 @@ spec:
status:
description: HumioBootstrapTokenStatus defines the observed state of HumioBootstrapToken.
properties:
bootstrapImage:
description: BootstrapImage is the image that was used to issue the
token
type: string
hashedTokenSecretStatus:
description: |-
HashedTokenSecret is the secret reference that contains the hashed token to use for this HumioBootstrapToken. This is set regardless of whether it's defined
Expand Down
7 changes: 7 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -3771,6 +3771,13 @@ HumioBootstrapTokenStatus defines the observed state of HumioBootstrapToken.
</tr>
</thead>
<tbody><tr>
<td><b>bootstrapImage</b></td>
<td>string</td>
<td>
BootstrapImage is the image that was used to issue the token<br/>
</td>
<td>false</td>
</tr><tr>
<td><b><a href="#humiobootstraptokenstatushashedtokensecretstatus">hashedTokenSecretStatus</a></b></td>
<td>object</td>
<td>
Expand Down
75 changes: 73 additions & 2 deletions internal/controller/humiobootstraptoken_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import (
"strings"
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/go-logr/logr"
humiov1alpha1 "github.com/humio/humio-operator/api/v1alpha1"
"github.com/humio/humio-operator/internal/helpers"
Expand Down Expand Up @@ -143,6 +145,11 @@ func (r *HumioBootstrapTokenReconciler) updateStatus(ctx context.Context, hbt *h
return r.Client.Status().Update(ctx, hbt)
}

func (r *HumioBootstrapTokenReconciler) updateStatusImage(ctx context.Context, hbt *humiov1alpha1.HumioBootstrapToken, image string) error {
hbt.Status.BootstrapImage = image
return r.Client.Status().Update(ctx, hbt)
}

func (r *HumioBootstrapTokenReconciler) execCommand(ctx context.Context, pod *corev1.Pod, args []string) (string, error) {
configLoader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(
clientcmd.NewDefaultClientConfigLoadingRules(),
Expand Down Expand Up @@ -209,7 +216,10 @@ func (r *HumioBootstrapTokenReconciler) createPod(ctx context.Context, hbt *humi
}
}
humioBootstrapTokenConfig := NewHumioBootstrapTokenConfig(hbt, humioCluster)
pod := ConstructBootstrapPod(&humioBootstrapTokenConfig)
pod, err := r.constructBootstrapPod(ctx, &humioBootstrapTokenConfig)
if err != nil {
return pod, r.logErrorAndReturn(err, "could not construct pod")
}
if err := r.Get(ctx, types.NamespacedName{
Namespace: pod.Namespace,
Name: pod.Name,
Expand All @@ -231,7 +241,10 @@ func (r *HumioBootstrapTokenReconciler) createPod(ctx context.Context, hbt *humi
func (r *HumioBootstrapTokenReconciler) deletePod(ctx context.Context, hbt *humiov1alpha1.HumioBootstrapToken, hc *humiov1alpha1.HumioCluster) error {
existingPod := &corev1.Pod{}
humioBootstrapTokenConfig := NewHumioBootstrapTokenConfig(hbt, hc)
pod := ConstructBootstrapPod(&humioBootstrapTokenConfig)
pod, err := r.constructBootstrapPod(ctx, &humioBootstrapTokenConfig)
if err != nil {
return r.logErrorAndReturn(err, "could not construct pod")
}
if err := r.Get(ctx, types.NamespacedName{
Namespace: pod.Namespace,
Name: pod.Name,
Expand Down Expand Up @@ -385,6 +398,11 @@ func (r *HumioBootstrapTokenReconciler) ensureBootstrapTokenHashedToken(ctx cont
if err = r.Update(ctx, updatedSecret); err != nil {
return r.logErrorAndReturn(err, "failed to update secret with hashedToken data")
}

if err := r.updateStatusImage(ctx, hbt, pod.Spec.Containers[0].Image); err != nil {
return r.logErrorAndReturn(err, "failed to update bootstrap token image status")
}

return nil
}

Expand All @@ -398,6 +416,59 @@ func (r *HumioBootstrapTokenReconciler) getBootstrapTokenSecret(ctx context.Cont
return existingSecret, err
}

func (r *HumioBootstrapTokenReconciler) constructBootstrapPod(ctx context.Context, bootstrapConfig *HumioBootstrapTokenConfig) (*corev1.Pod, error) {
userID := int64(65534)
var image string

if bootstrapConfig.imageSource() == nil {
image = bootstrapConfig.image()
} else {
configMap, err := kubernetes.GetConfigMap(ctx, r, bootstrapConfig.imageSource().ConfigMapRef.Name, bootstrapConfig.namespace())
if err != nil {
return &corev1.Pod{}, r.logErrorAndReturn(err, "failed to get imageFromSource")
}
if imageValue, ok := configMap.Data[bootstrapConfig.imageSource().ConfigMapRef.Key]; ok {
image = imageValue
}
}

return &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: bootstrapConfig.podName(),
Namespace: bootstrapConfig.namespace(),
},
Spec: corev1.PodSpec{
ImagePullSecrets: bootstrapConfig.imagePullSecrets(),
Affinity: bootstrapConfig.affinity(),
Containers: []corev1.Container{
{
Name: HumioContainerName,
Image: image,
Command: []string{"/bin/sleep", "900"},
Env: []corev1.EnvVar{
{
Name: "HUMIO_LOG4J_CONFIGURATION",
Value: "log4j2-json-stdout.xml",
},
},
Resources: bootstrapConfig.resources(),
SecurityContext: &corev1.SecurityContext{
Privileged: helpers.BoolPtr(false),
AllowPrivilegeEscalation: helpers.BoolPtr(false),
ReadOnlyRootFilesystem: helpers.BoolPtr(true),
RunAsUser: &userID,
Capabilities: &corev1.Capabilities{
Drop: []corev1.Capability{
"ALL",
},
},
},
},
},
},
}, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *HumioBootstrapTokenReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
Expand Down
19 changes: 18 additions & 1 deletion internal/controller/humiobootstraptoken_defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,29 @@ func (b *HumioBootstrapTokenConfig) image() string {
}
if b.ManagedHumioCluster != nil {
if len(b.ManagedHumioCluster.Spec.NodePools) > 0 {
return b.ManagedHumioCluster.Spec.NodePools[0].Image
if b.ManagedHumioCluster.Spec.NodePools[0].Image != "" {
return b.ManagedHumioCluster.Spec.NodePools[0].Image
}
}
}
return versions.DefaultHumioImageVersion()
}

func (b *HumioBootstrapTokenConfig) imageSource() *humiov1alpha1.HumioImageSource {

if b.ManagedHumioCluster.Spec.ImageSource != nil {
return b.ManagedHumioCluster.Spec.ImageSource
}
if b.ManagedHumioCluster != nil {
if len(b.ManagedHumioCluster.Spec.NodePools) > 0 {
if b.ManagedHumioCluster.Spec.NodePools[0].ImageSource != nil {
return b.ManagedHumioCluster.Spec.NodePools[0].ImageSource
}
}
}
return nil
}

func (b *HumioBootstrapTokenConfig) imagePullSecrets() []corev1.LocalObjectReference {
if len(b.BootstrapToken.Spec.ImagePullSecrets) > 0 {
return b.BootstrapToken.Spec.ImagePullSecrets
Expand Down
4 changes: 3 additions & 1 deletion internal/controller/humiobootstraptoken_pods.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package controller

import (
"context"

"github.com/humio/humio-operator/internal/helpers"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func ConstructBootstrapPod(bootstrapConfig *HumioBootstrapTokenConfig) *corev1.Pod {
func ConstructBootstrapPod(ctx context.Context, bootstrapConfig *HumioBootstrapTokenConfig) *corev1.Pod {
userID := int64(65534)
return &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,56 @@ var _ = Describe("HumioCluster Controller", func() {
})
})

Context("Humio Cluster Create with Image Source", Label("envtest", "dummy", "real"), func() {
It("Should correctly create cluster from image source", func() {
key := types.NamespacedName{
Name: "humiocluster-create-image-source",
Namespace: testProcessNamespace,
}
toCreate := suite.ConstructBasicSingleNodeHumioCluster(key, true)
toCreate.Spec.Image = ""
toCreate.Spec.NodeCount = 2
toCreate.Spec.ImageSource = &humiov1alpha1.HumioImageSource{
ConfigMapRef: &corev1.ConfigMapKeySelector{
LocalObjectReference: corev1.LocalObjectReference{
Name: "image-source-create",
},
Key: "tag",
},
}

ctx := context.Background()
var updatedHumioCluster humiov1alpha1.HumioCluster

suite.UsingClusterBy(key.Name, "Creating the imageSource configmap")
updatedImage := versions.UpgradePatchBestEffortNewVersion()
envVarSourceConfigMap := corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "image-source-create",
Namespace: key.Namespace,
},
Data: map[string]string{"tag": updatedImage},
}
Expect(k8sClient.Create(ctx, &envVarSourceConfigMap)).To(Succeed())

suite.UsingClusterBy(key.Name, "Creating the cluster successfully")
suite.CreateAndBootstrapCluster(ctx, k8sClient, testHumioClient, toCreate, true, humiov1alpha1.HumioClusterStateRunning, testTimeout)
defer suite.CleanupCluster(ctx, k8sClient, toCreate)

Eventually(func() string {
updatedHumioCluster = humiov1alpha1.HumioCluster{}
Expect(k8sClient.Get(ctx, key, &updatedHumioCluster)).Should(Succeed())
return updatedHumioCluster.Status.State
}, testTimeout, suite.TestInterval).Should(BeIdenticalTo(humiov1alpha1.HumioClusterStateRunning))

Eventually(func() error {
bootstrapToken, err := suite.GetHumioBootstrapToken(ctx, key, k8sClient)
Expect(bootstrapToken.Status.BootstrapImage).To(BeEquivalentTo(updatedImage))
return err
}, testTimeout, suite.TestInterval).Should(Succeed())
})
})

Context("Humio Cluster Update Image Source", Label("envtest", "dummy", "real"), func() {
It("Update should correctly replace pods to use new image", func() {
key := types.NamespacedName{
Expand Down Expand Up @@ -4012,7 +4062,7 @@ var _ = Describe("HumioCluster Controller", func() {
return k8sClient.Update(ctx, &updatedHumioCluster)
}, testTimeout, suite.TestInterval).Should(Succeed())

suite.SimulateHumioBootstrapTokenCreatingSecretAndUpdatingStatus(ctx, key, k8sClient, testTimeout)
suite.SimulateHumioBootstrapTokenCreatingSecretAndUpdatingStatus(ctx, key, k8sClient, testTimeout, &updatedHumioCluster)

suite.UsingClusterBy(key.Name, "Confirming we only created ingresses with expected hostname")
foundIngressList = []networkingv1.Ingress{}
Expand Down
57 changes: 46 additions & 11 deletions internal/controller/suite/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ func CreateAndBootstrapCluster(ctx context.Context, k8sClient client.Client, hum
return
}

SimulateHumioBootstrapTokenCreatingSecretAndUpdatingStatus(ctx, key, k8sClient, testTimeout)
SimulateHumioBootstrapTokenCreatingSecretAndUpdatingStatus(ctx, key, k8sClient, testTimeout, cluster)

UsingClusterBy(key.Name, "Confirming cluster enters running state")
var updatedHumioCluster humiov1alpha1.HumioCluster
Expand Down Expand Up @@ -718,21 +718,41 @@ func CreateDockerRegredSecret(ctx context.Context, namespace corev1.Namespace, k
Expect(k8sClient.Create(ctx, &regcredSecret)).To(Succeed())
}

func SimulateHumioBootstrapTokenCreatingSecretAndUpdatingStatus(ctx context.Context, key types.NamespacedName, k8sClient client.Client, testTimeout time.Duration) {
func SimulateHumioBootstrapTokenCreatingSecretAndUpdatingStatus(ctx context.Context, key types.NamespacedName, k8sClient client.Client, testTimeout time.Duration, cluster *humiov1alpha1.HumioCluster) {
UsingClusterBy(key.Name, "Simulating HumioBootstrapToken Controller running and adding the secret and status")
Eventually(func() error {
hbtList, err := kubernetes.ListHumioBootstrapTokens(ctx, k8sClient, key.Namespace, kubernetes.LabelsForHumioBootstrapToken(key.Name))
if err != nil {
return err
var bootstrapImage string
bootstrapImage = "test"
if cluster.Spec.Image != "" {
bootstrapImage = cluster.Spec.Image
}
if len(hbtList) == 0 {
return fmt.Errorf("no humiobootstraptokens for cluster %s", key.Name)
if cluster.Spec.ImageSource != nil {
configMap, err := kubernetes.GetConfigMap(ctx, k8sClient, cluster.Spec.ImageSource.ConfigMapRef.Name, cluster.Namespace)
if err != nil && !k8serrors.IsNotFound(err) {
Expect(err).Should(Succeed())
} else {
bootstrapImage = configMap.Data[cluster.Spec.ImageSource.ConfigMapRef.Key]
}
}
if len(hbtList) > 1 {
return fmt.Errorf("too many humiobootstraptokens for cluster %s. found list : %+v", key.Name, hbtList)
for _, nodePool := range cluster.Spec.NodePools {
if nodePool.HumioNodeSpec.Image != "" {
bootstrapImage = nodePool.HumioNodeSpec.Image
break
}
if nodePool.ImageSource != nil {
configMap, err := kubernetes.GetConfigMap(ctx, k8sClient, nodePool.ImageSource.ConfigMapRef.Name, cluster.Namespace)
if err != nil && !k8serrors.IsNotFound(err) {
Expect(err).Should(Succeed())
} else {
bootstrapImage = configMap.Data[nodePool.ImageSource.ConfigMapRef.Key]
break
}
}
}
updatedHumioBootstrapToken, err := GetHumioBootstrapToken(ctx, key, k8sClient)
if err != nil {
return err
}

updatedHumioBootstrapToken := hbtList[0]
updatedHumioBootstrapToken.Status.State = humiov1alpha1.HumioBootstrapTokenStateReady
updatedHumioBootstrapToken.Status.TokenSecretKeyRef = humiov1alpha1.HumioTokenSecretStatus{
SecretKeyRef: &corev1.SecretKeySelector{
Expand All @@ -750,6 +770,21 @@ func SimulateHumioBootstrapTokenCreatingSecretAndUpdatingStatus(ctx context.Cont
Key: "hashedToken",
},
}
updatedHumioBootstrapToken.Status.BootstrapImage = bootstrapImage
return k8sClient.Status().Update(ctx, &updatedHumioBootstrapToken)
}, testTimeout, TestInterval).Should(Succeed())
}

func GetHumioBootstrapToken(ctx context.Context, key types.NamespacedName, k8sClient client.Client) (humiov1alpha1.HumioBootstrapToken, error) {
hbtList, err := kubernetes.ListHumioBootstrapTokens(ctx, k8sClient, key.Namespace, kubernetes.LabelsForHumioBootstrapToken(key.Name))
if err != nil {
return humiov1alpha1.HumioBootstrapToken{}, err
}
if len(hbtList) == 0 {
return humiov1alpha1.HumioBootstrapToken{}, fmt.Errorf("no humiobootstraptokens for cluster %s", key.Name)
}
if len(hbtList) > 1 {
return humiov1alpha1.HumioBootstrapToken{}, fmt.Errorf("too many humiobootstraptokens for cluster %s. found list : %+v", key.Name, hbtList)
}
return hbtList[0], nil
}
Loading