Skip to content
Merged
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 .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"gopls": {
"formatting.gofumpt": true
},
// https://github.com/golang/vscode-go/wiki/features#analyze-vulnerabilities-in-dependencies
"go.diagnostic.vulncheck": "Imports",

// https://github.com/segmentio/golines#visual-studio-code
"emeraldwalk.runonsave": {
Expand Down
2 changes: 1 addition & 1 deletion internal/api/v1alpha1/pod_access_request_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func (r *PodAccessRequest) GetUptime() time.Duration {

// SetPodName conforms to the interfaces.OzRequestResource interface
func (r *PodAccessRequest) SetPodName(name string) error {
if r.Status.PodName != "" {
if (r.Status.PodName != "") && (r.Status.PodName != name) {
return fmt.Errorf(
"immutable field Status.PodName already set (%s), cannot update to %s",
r.Status.PodName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ import (
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
v1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"

"github.com/diranged/oz/internal/api/v1alpha1"
"github.com/diranged/oz/internal/builders/execaccessbuilder/internal"
bldutil "github.com/diranged/oz/internal/builders/utils"
"github.com/diranged/oz/internal/testing/utils"
)

Expand All @@ -23,7 +26,7 @@ var _ = Describe("RequestReconciler", Ordered, func() {
ctx = context.Background()
ns *corev1.Namespace
deployment *appsv1.Deployment
pod *v1.Pod
pod *corev1.Pod
request *v1alpha1.ExecAccessRequest
template *v1alpha1.ExecAccessTemplate
builder = ExecAccessBuilder{}
Expand All @@ -34,7 +37,7 @@ var _ = Describe("RequestReconciler", Ordered, func() {

BeforeAll(func() {
By("Should have a namespace to execute tests in")
ns = &v1.Namespace{
ns = &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: utils.RandomString(8),
},
Expand Down Expand Up @@ -75,7 +78,7 @@ var _ = Describe("RequestReconciler", Ordered, func() {
Expect(err).To(Not(HaveOccurred()))

By("Create a single Pod that should match the Deployment spec above for testing")
pod = &v1.Pod{
pod = &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: utils.RandomString(8),
Namespace: ns.GetName(),
Expand Down Expand Up @@ -178,13 +181,40 @@ var _ = Describe("RequestReconciler", Ordered, func() {
It("CreateAccessResources() should succeed with random pod selection", func() {
request.Status.PodName = ""
request.Spec.TargetPod = ""

ret, err := builder.CreateAccessResources(ctx, k8sClient, request, template)

// VERIFY: No error returned
Expect(err).ToNot(HaveOccurred())

// VERIFY: Proper status string returned
Expect(ret).To(MatchRegexp(fmt.Sprintf(
"Success. Role %s-.*, RoleBinding %s.* created",
request.GetName(),
request.GetName(),
)))

// VERIFY: Role Created as expected
foundRole := &rbacv1.Role{}
err = k8sClient.Get(ctx, types.NamespacedName{
Name: bldutil.GenerateResourceName(request),
Namespace: ns.GetName(),
}, foundRole)
Expect(err).ToNot(HaveOccurred())
Expect(foundRole.GetOwnerReferences()).ToNot(BeNil())
Expect(foundRole.Rules[0].ResourceNames[0]).To(Equal(pod.GetName()))
Expect(foundRole.Rules[1].ResourceNames[0]).To(Equal(pod.GetName()))

// VERIFY: RoleBinding Created as expected
foundRoleBinding := &rbacv1.RoleBinding{}
err = k8sClient.Get(ctx, types.NamespacedName{
Name: bldutil.GenerateResourceName(request),
Namespace: ns.GetName(),
}, foundRoleBinding)
Expect(err).ToNot(HaveOccurred())
Expect(foundRoleBinding.GetOwnerReferences()).ToNot(BeNil())
Expect(foundRoleBinding.RoleRef.Name).To(Equal(foundRole.GetName()))
Expect(foundRoleBinding.Subjects[0].Name).To(Equal("foo"))
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func getRandomPod(
Selector: selector,
},
client.MatchingFields{
v1alpha1.FieldSelectorStatusPhase: PodPhaseRunning,
v1alpha1.FieldSelectorStatusPhase: string(PodPhaseRunning),
},
}
if err := cl.List(ctx, podList, opts...); err != nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func getSpecificPod(
},
client.MatchingFields{
v1alpha1.FieldSelectorMetadataName: podName,
v1alpha1.FieldSelectorStatusPhase: PodPhaseRunning,
v1alpha1.FieldSelectorStatusPhase: string(PodPhaseRunning),
},
}
if err := cl.List(ctx, podList, opts...); err != nil {
Expand Down
6 changes: 5 additions & 1 deletion internal/builders/execaccessbuilder/internal/vars.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package internal

import (
corev1 "k8s.io/api/core/v1"
)

// PodPhaseRunning is exposed here so that we can reconfigure the search during
// tests to look for Pending pods.
var PodPhaseRunning = "Running"
var PodPhaseRunning = corev1.PodRunning
3 changes: 3 additions & 0 deletions internal/builders/execaccessbuilder/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import (
//+kubebuilder:rbac:groups=crds.wizardofoz.co,resources=execaccessrequests/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=crds.wizardofoz.co,resources=execaccessrequests/finalizers,verbs=update

//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles,verbs=get;list;watch;create;update;patch;delete;bind;escalate
//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=get;list;watch;create;update;patch;delete

// ExecAccessBuilder implements the IBuilder interface for ExecAccessRequest resources
type ExecAccessBuilder struct{}

Expand Down
132 changes: 132 additions & 0 deletions internal/builders/podaccessbuilder/access_resources_are_ready.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package podaccessbuilder

import (
"context"
"errors"
"fmt"
"time"

"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
logf "sigs.k8s.io/controller-runtime/pkg/log"

"github.com/diranged/oz/internal/api/v1alpha1"
)

// AccessResourcesAreReady implements the IBuilder interface by checking for
// the current state of the Pod for the user and returning True when it is
// ready, or False if it is not ready after a specified timeout.
//
// TODO: Implement a per-pod-access-template setting to tune this timeout.
func (b *PodAccessBuilder) AccessResourcesAreReady(
ctx context.Context,
client client.Client,
req v1alpha1.IRequestResource,
_ v1alpha1.ITemplateResource,
) (bool, error) {
log := logf.FromContext(ctx).WithName("AccessResourcesAreReady")

// Cast the Request into an PodAccessRequest.
podReq := req.(*v1alpha1.PodAccessRequest)

// First, verify whether or not the PodName field has been set. If not,
// then some part of the reconciliation has previously failed.
if podReq.GetPodName() == "" {
return false, errors.New("status.podName not yet set")
}

// This empty Pod struct will be filled in by the isPodReady() function.
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: podReq.GetPodName(),
Namespace: podReq.GetNamespace(),
},
}

log.Info(
fmt.Sprintf(
"Checking if pod %s is ready yet (timeout: %s)",
pod.GetName(),
defaultReadyWaitTime,
),
)

// Store the ready/err states outside of the loop so that we can return
// them at the end of the method.
var ready bool
var err error

// In a loop, keep checking the Pod state. When it's ready, return. In an
// error, just keep looping. After the timeout has occurrred, we simply
// return the last known state.
for stay, timeout := true, time.After(defaultReadyWaitTime); stay; {
select {
case <-timeout:
log.Info(fmt.Sprintf("Timeout waiting for %s to become ready.", pod.GetName()))
stay = false
default:
if ready, err = isPodReady(ctx, client, log, pod); err != nil {
if apierrors.IsNotFound(err) {
// Immediately bail out and let a requeue event happen
return false, err
}
// For any other error, consider it transient and try again
log.Error(err, "Error getting Pod status (will retry)")
} else if ready {
// return ready = true, and no error
log.Info("Pod ready state", "phase", pod.Status.Phase)
return ready, nil
}
}

// Wait 1 second before trying again
log.V(1).Info("Sleeping and trying again")
time.Sleep(defaultReadyWaitInterval)
}

return ready, nil
}

func isPodReady(
ctx context.Context,
client client.Client,
log logr.Logger,
pod *corev1.Pod,
) (bool, error) {
// Next, get the Pod. If the pod-get fails, then we need to return that failure.
log.V(2).Info("Getting pod")
err := client.Get(ctx, types.NamespacedName{
Name: pod.GetName(),
Namespace: pod.GetNamespace(),
}, pod)
if err != nil {
return false, err
}

// Now, check the Pod Phase first... the pod could be Pending and not yet
// ready to even have a condition state.
if pod.Status.Phase != PodPhaseRunning {
log.V(2).Info(fmt.Sprintf("Pod Phase is %s, not %s", pod.Status.Phase, PodPhaseRunning))
return false, nil
}

// Iterate through the PodConditions looking for the PodReady condition.
// When we find it, return whether it's "True" or "False".
conditions := pod.Status.Conditions
for _, condition := range conditions {
if condition.Type == corev1.PodReady {
// val = condition.Status == corev1.ConditionTrue
log.V(2).
Info(fmt.Sprintf("Got to the inner condition... returning %s", condition.Status))
return condition.Status == corev1.ConditionTrue, nil
}
}

// Return ready=false at this point
log.V(2).Info("Pod Ready Condition not yet True")
return false, nil
}
Loading