mirror of
https://github.com/stakater/Reloader.git
synced 2026-05-17 06:06:39 +00:00
266 lines
9.3 KiB
Go
266 lines
9.3 KiB
Go
package utils
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
|
"k8s.io/client-go/discovery"
|
|
"k8s.io/client-go/dynamic"
|
|
)
|
|
|
|
// DeploymentConfigGVR returns the GroupVersionResource for OpenShift DeploymentConfigs.
|
|
var DeploymentConfigGVR = schema.GroupVersionResource{
|
|
Group: "apps.openshift.io",
|
|
Version: "v1",
|
|
Resource: "deploymentconfigs",
|
|
}
|
|
|
|
// DCOption is a functional option for configuring a DeploymentConfig.
|
|
type DCOption func(*unstructured.Unstructured)
|
|
|
|
// HasDeploymentConfigSupport checks if the cluster has OpenShift DeploymentConfig API available.
|
|
func HasDeploymentConfigSupport(discoveryClient discovery.DiscoveryInterface) bool {
|
|
_, apiLists, err := discoveryClient.ServerGroupsAndResources()
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
for _, apiList := range apiLists {
|
|
for _, resource := range apiList.APIResources {
|
|
if resource.Kind == "DeploymentConfig" {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// CreateDeploymentConfig creates an OpenShift DeploymentConfig with the given options.
|
|
func CreateDeploymentConfig(ctx context.Context, dynamicClient dynamic.Interface, namespace, name string, opts ...DCOption) error {
|
|
dc := &unstructured.Unstructured{
|
|
Object: map[string]interface{}{
|
|
"apiVersion": "apps.openshift.io/v1",
|
|
"kind": "DeploymentConfig",
|
|
"metadata": map[string]interface{}{
|
|
"name": name,
|
|
"namespace": namespace,
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"replicas": int64(1),
|
|
"selector": map[string]interface{}{
|
|
"app": name,
|
|
},
|
|
"template": map[string]interface{}{
|
|
"metadata": map[string]interface{}{
|
|
"labels": map[string]interface{}{
|
|
"app": name,
|
|
},
|
|
},
|
|
"spec": map[string]interface{}{
|
|
"containers": []interface{}{
|
|
map[string]interface{}{
|
|
"name": "app",
|
|
"image": "busybox:1.36",
|
|
"command": []interface{}{"sh", "-c", "sleep 3600"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"triggers": []interface{}{
|
|
map[string]interface{}{
|
|
"type": "ConfigChange",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
// Apply options
|
|
for _, opt := range opts {
|
|
opt(dc)
|
|
}
|
|
|
|
_, err := dynamicClient.Resource(DeploymentConfigGVR).Namespace(namespace).Create(ctx, dc, metav1.CreateOptions{})
|
|
return err
|
|
}
|
|
|
|
// DeleteDeploymentConfig deletes a DeploymentConfig.
|
|
func DeleteDeploymentConfig(ctx context.Context, dynamicClient dynamic.Interface, namespace, name string) error {
|
|
return dynamicClient.Resource(DeploymentConfigGVR).Namespace(namespace).Delete(ctx, name, metav1.DeleteOptions{})
|
|
}
|
|
|
|
// GetDeploymentConfig retrieves a DeploymentConfig.
|
|
func GetDeploymentConfig(ctx context.Context, dynamicClient dynamic.Interface, namespace, name string) (*unstructured.Unstructured, error) {
|
|
return dynamicClient.Resource(DeploymentConfigGVR).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
|
|
}
|
|
|
|
// WithDCConfigMapEnvFrom adds a ConfigMap envFrom to the DeploymentConfig.
|
|
func WithDCConfigMapEnvFrom(configMapName string) DCOption {
|
|
return func(dc *unstructured.Unstructured) {
|
|
containers, _, _ := unstructured.NestedSlice(dc.Object, "spec", "template", "spec", "containers")
|
|
if len(containers) > 0 {
|
|
container := containers[0].(map[string]interface{})
|
|
envFrom, _, _ := unstructured.NestedSlice(container, "envFrom")
|
|
envFrom = append(envFrom, map[string]interface{}{
|
|
"configMapRef": map[string]interface{}{
|
|
"name": configMapName,
|
|
},
|
|
})
|
|
container["envFrom"] = envFrom
|
|
containers[0] = container
|
|
_ = unstructured.SetNestedSlice(dc.Object, containers, "spec", "template", "spec", "containers")
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithDCSecretEnvFrom adds a Secret envFrom to the DeploymentConfig.
|
|
func WithDCSecretEnvFrom(secretName string) DCOption {
|
|
return func(dc *unstructured.Unstructured) {
|
|
containers, _, _ := unstructured.NestedSlice(dc.Object, "spec", "template", "spec", "containers")
|
|
if len(containers) > 0 {
|
|
container := containers[0].(map[string]interface{})
|
|
envFrom, _, _ := unstructured.NestedSlice(container, "envFrom")
|
|
envFrom = append(envFrom, map[string]interface{}{
|
|
"secretRef": map[string]interface{}{
|
|
"name": secretName,
|
|
},
|
|
})
|
|
container["envFrom"] = envFrom
|
|
containers[0] = container
|
|
_ = unstructured.SetNestedSlice(dc.Object, containers, "spec", "template", "spec", "containers")
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithDCConfigMapVolume adds a ConfigMap volume to the DeploymentConfig.
|
|
func WithDCConfigMapVolume(configMapName string) DCOption {
|
|
return func(dc *unstructured.Unstructured) {
|
|
// Add volume
|
|
volumes, _, _ := unstructured.NestedSlice(dc.Object, "spec", "template", "spec", "volumes")
|
|
volumes = append(volumes, map[string]interface{}{
|
|
"name": configMapName + "-volume",
|
|
"configMap": map[string]interface{}{
|
|
"name": configMapName,
|
|
},
|
|
})
|
|
_ = unstructured.SetNestedSlice(dc.Object, volumes, "spec", "template", "spec", "volumes")
|
|
|
|
// Add volumeMount
|
|
containers, _, _ := unstructured.NestedSlice(dc.Object, "spec", "template", "spec", "containers")
|
|
if len(containers) > 0 {
|
|
container := containers[0].(map[string]interface{})
|
|
volumeMounts, _, _ := unstructured.NestedSlice(container, "volumeMounts")
|
|
volumeMounts = append(volumeMounts, map[string]interface{}{
|
|
"name": configMapName + "-volume",
|
|
"mountPath": "/etc/config/" + configMapName,
|
|
})
|
|
container["volumeMounts"] = volumeMounts
|
|
containers[0] = container
|
|
_ = unstructured.SetNestedSlice(dc.Object, containers, "spec", "template", "spec", "containers")
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithDCSecretVolume adds a Secret volume to the DeploymentConfig.
|
|
func WithDCSecretVolume(secretName string) DCOption {
|
|
return func(dc *unstructured.Unstructured) {
|
|
// Add volume
|
|
volumes, _, _ := unstructured.NestedSlice(dc.Object, "spec", "template", "spec", "volumes")
|
|
volumes = append(volumes, map[string]interface{}{
|
|
"name": secretName + "-volume",
|
|
"secret": map[string]interface{}{
|
|
"secretName": secretName,
|
|
},
|
|
})
|
|
_ = unstructured.SetNestedSlice(dc.Object, volumes, "spec", "template", "spec", "volumes")
|
|
|
|
// Add volumeMount
|
|
containers, _, _ := unstructured.NestedSlice(dc.Object, "spec", "template", "spec", "containers")
|
|
if len(containers) > 0 {
|
|
container := containers[0].(map[string]interface{})
|
|
volumeMounts, _, _ := unstructured.NestedSlice(container, "volumeMounts")
|
|
volumeMounts = append(volumeMounts, map[string]interface{}{
|
|
"name": secretName + "-volume",
|
|
"mountPath": "/etc/secrets/" + secretName,
|
|
})
|
|
container["volumeMounts"] = volumeMounts
|
|
containers[0] = container
|
|
_ = unstructured.SetNestedSlice(dc.Object, containers, "spec", "template", "spec", "containers")
|
|
}
|
|
}
|
|
}
|
|
|
|
// WithDCAnnotations adds annotations to the DeploymentConfig's pod template.
|
|
func WithDCAnnotations(annotations map[string]string) DCOption {
|
|
return func(dc *unstructured.Unstructured) {
|
|
annotationsMap := make(map[string]interface{})
|
|
for k, v := range annotations {
|
|
annotationsMap[k] = v
|
|
}
|
|
_ = unstructured.SetNestedMap(dc.Object, annotationsMap, "spec", "template", "metadata", "annotations")
|
|
}
|
|
}
|
|
|
|
// WaitForDeploymentConfigReady waits for a DeploymentConfig to be ready.
|
|
func WaitForDeploymentConfigReady(ctx context.Context, dynamicClient dynamic.Interface, namespace, name string, timeout time.Duration) error {
|
|
return wait.PollUntilContextTimeout(ctx, DefaultInterval, timeout, true, func(ctx context.Context) (bool, error) {
|
|
dc, err := dynamicClient.Resource(DeploymentConfigGVR).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
|
|
if err != nil {
|
|
return false, nil // Keep polling
|
|
}
|
|
|
|
// Check replicas == readyReplicas
|
|
replicas, _, _ := unstructured.NestedInt64(dc.Object, "spec", "replicas")
|
|
readyReplicas, _, _ := unstructured.NestedInt64(dc.Object, "status", "readyReplicas")
|
|
|
|
if replicas > 0 && replicas == readyReplicas {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
})
|
|
}
|
|
|
|
// WaitForDeploymentConfigReloaded waits for a DeploymentConfig's pod template to have the reloader annotation.
|
|
func WaitForDeploymentConfigReloaded(ctx context.Context, dynamicClient dynamic.Interface, namespace, name, annotationKey string, timeout time.Duration) (bool, error) {
|
|
var found bool
|
|
err := wait.PollUntilContextTimeout(ctx, DefaultInterval, timeout, true, func(ctx context.Context) (bool, error) {
|
|
dc, err := dynamicClient.Resource(DeploymentConfigGVR).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
|
|
if err != nil {
|
|
return false, nil
|
|
}
|
|
|
|
// Check pod template annotations
|
|
annotations, _, _ := unstructured.NestedStringMap(dc.Object, "spec", "template", "metadata", "annotations")
|
|
if annotations != nil {
|
|
if _, ok := annotations[annotationKey]; ok {
|
|
found = true
|
|
return true, nil
|
|
}
|
|
}
|
|
|
|
return false, nil
|
|
})
|
|
|
|
if err != nil && err != context.DeadlineExceeded {
|
|
return false, err
|
|
}
|
|
return found, nil
|
|
}
|
|
|
|
// GetDeploymentConfigPodTemplateAnnotations retrieves the pod template annotations from a DeploymentConfig.
|
|
func GetDeploymentConfigPodTemplateAnnotations(ctx context.Context, dynamicClient dynamic.Interface, namespace, name string) (map[string]string, error) {
|
|
dc, err := dynamicClient.Resource(DeploymentConfigGVR).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
annotations, _, _ := unstructured.NestedStringMap(dc.Object, "spec", "template", "metadata", "annotations")
|
|
return annotations, nil
|
|
}
|