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

feat: introduce emulation for sc, pv and pvc #38

Merged
merged 5 commits into from
Sep 4, 2023
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 cmd/k2d.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ func main() {
container.Add(apis.Events())
// /apis/authorization.k8s.io
container.Add(apis.Authorization())
// /apis/storage.k8s.io
container.Add(apis.Storages())

k2d := k2d.NewK2DAPI(serverConfiguration, kubeDockerAdapter)
// /k2d/kubeconfig
Expand Down
8 changes: 8 additions & 0 deletions internal/adapter/converter/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,9 @@ func (converter *DockerAPIConverter) setVolumeMounts(namespace string, hostConfi
// For HostPath:
// The function directly uses the HostPath and volume mount to set up the bind in the Docker host configuration.
//
// For PersistentVolumeClaim:
// The function uses the volume name and namespace to generate a unique name for the volume.
//
// Parameters:
// - hostConfig: The Docker host configuration where the volume binds will be set.
// - volume: The Kubernetes volume object to be processed.
Expand Down Expand Up @@ -416,6 +419,11 @@ func (converter *DockerAPIConverter) handleVolumeSource(namespace string, hostCo
} else if volume.HostPath != nil {
bind := fmt.Sprintf("%s:%s", volume.HostPath.Path, volumeMount.MountPath)
hostConfig.Binds = append(hostConfig.Binds, bind)
} else if volume.VolumeSource.PersistentVolumeClaim != nil {
// TODO: Replace the fmt.Sprintf("k2d-pv-%s-%s", namespace, volume.VolumeSource.PersistentVolumeClaim.ClaimName)
// part with the buildPersistentVolumeName function.
bind := fmt.Sprintf("%s:%s", fmt.Sprintf("k2d-pv-%s-%s", namespace, volume.VolumeSource.PersistentVolumeClaim.ClaimName), volumeMount.MountPath)
hostConfig.Binds = append(hostConfig.Binds, bind)
}
return nil
}
99 changes: 99 additions & 0 deletions internal/adapter/converter/volume.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package converter

import (
"fmt"
"time"

"github.com/docker/docker/api/types/volume"
k2dtypes "github.com/portainer/k2d/internal/adapter/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/apis/core"
)

func (converter *DockerAPIConverter) ConvertVolumeToPersistentVolume(volume volume.Volume) (*core.PersistentVolume, error) {
creationDate, err := time.Parse(time.RFC3339, volume.CreatedAt)
if err != nil {
return nil, fmt.Errorf("unable to parse volume creation date: %w", err)
}

persistentVolumeClaimReference := &core.ObjectReference{
Kind: "PersistentVolumeClaim",
Namespace: volume.Labels[k2dtypes.NamespaceLabelKey],
Name: volume.Labels[k2dtypes.PersistentVolumeClaimLabelKey],
}

return &core.PersistentVolume{
ObjectMeta: metav1.ObjectMeta{
Name: volume.Name,
CreationTimestamp: metav1.Time{
Time: creationDate,
},
},
TypeMeta: metav1.TypeMeta{
Kind: "PersistentVolume",
APIVersion: "v1",
},
Spec: core.PersistentVolumeSpec{
// Capacity: resourceList,
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
},
PersistentVolumeReclaimPolicy: core.PersistentVolumeReclaimDelete,
PersistentVolumeSource: core.PersistentVolumeSource{
HostPath: &core.HostPathVolumeSource{
Path: volume.Mountpoint,
},
},
ClaimRef: persistentVolumeClaimReference,
StorageClassName: "local",
},
Status: core.PersistentVolumeStatus{
Phase: core.VolumeBound,
},
}, nil
}

func (converter *DockerAPIConverter) UpdateVolumeToPersistentVolumeClaim(persistentVolumeClaim *core.PersistentVolumeClaim, volume volume.Volume) error {
creationDate, err := time.Parse(time.RFC3339, volume.CreatedAt)
if err != nil {
return fmt.Errorf("unable to parse volume creation date: %w", err)
}

storageClassName := "local"

persistentVolumeClaim.TypeMeta = metav1.TypeMeta{
Kind: "PersistentVolumeClaim",
APIVersion: "v1",
}

persistentVolumeClaim.ObjectMeta = metav1.ObjectMeta{
Name: volume.Labels[k2dtypes.PersistentVolumeClaimLabelKey],
Namespace: volume.Labels[k2dtypes.NamespaceLabelKey],
CreationTimestamp: metav1.Time{
Time: creationDate,
},
Annotations: map[string]string{
"kubectl.kubernetes.io/last-applied-configuration": volume.Labels[k2dtypes.PersistentVolumeClaimLastAppliedConfigLabelKey],
},
}

persistentVolumeClaim.Spec = core.PersistentVolumeClaimSpec{
StorageClassName: &storageClassName,
// TODO: Replace the fmt.Sprintf("k2d-pv-%s-%s", namespace, volume.VolumeSource.PersistentVolumeClaim.ClaimName)
// part with the buildPersistentVolumeName function.
VolumeName: fmt.Sprintf("k2d-pv-%s-%s", volume.Labels[k2dtypes.NamespaceLabelKey], volume.Labels[k2dtypes.PersistentVolumeClaimLabelKey]),
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
},
Resources: persistentVolumeClaim.Spec.Resources,
}

persistentVolumeClaim.Status = core.PersistentVolumeClaimStatus{
Phase: core.ClaimBound,
AccessModes: []core.PersistentVolumeAccessMode{
core.ReadWriteOnce,
},
}

return nil
}
1 change: 1 addition & 0 deletions internal/adapter/namespace.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func (adapter *KubeDockerAdapter) DeleteNamespace(ctx context.Context, namespace
}

func (adapter *KubeDockerAdapter) GetNamespace(ctx context.Context, namespaceName string) (*corev1.Namespace, error) {

networkName := buildNetworkName(namespaceName)

network, err := adapter.getNetwork(ctx, networkName)
Expand Down
6 changes: 6 additions & 0 deletions internal/adapter/naming.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,9 @@ func buildContainerName(containerName, namespace string) string {
func buildNetworkName(namespace string) string {
return fmt.Sprintf("k2d-%s", namespace)
}

// Each persistentVolume is named using the following format:
// k2d-pv-[namespace]-[volume-name]
func buildPersistentVolumeName(volumeName string, namespace string) string {
return fmt.Sprintf("k2d-pv-%s-%s", namespace, volumeName)
}
92 changes: 92 additions & 0 deletions internal/adapter/persistentvolume.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package adapter

import (
"context"
"fmt"

"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/errdefs"
adaptererr "github.com/portainer/k2d/internal/adapter/errors"
k2dtypes "github.com/portainer/k2d/internal/adapter/types"
"github.com/portainer/k2d/internal/k8s"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/apis/core"
)

func (adapter *KubeDockerAdapter) GetPersistentVolume(ctx context.Context, persistentVolumeName string) (*corev1.PersistentVolume, error) {
volume, err := adapter.cli.VolumeInspect(ctx, persistentVolumeName)
if err != nil {
if errdefs.IsNotFound(err) {
return nil, adaptererr.ErrResourceNotFound
}
return nil, fmt.Errorf("unable to inspect docker volume %s: %w", persistentVolumeName, err)
}

persistentVolume, err := adapter.converter.ConvertVolumeToPersistentVolume(volume)
if err != nil {
return nil, fmt.Errorf("unable to convert Docker volume to PersistentVolume: %w", err)
}

versionedPersistentVolume := corev1.PersistentVolume{
TypeMeta: metav1.TypeMeta{
Kind: "PersistentVolume",
APIVersion: "v1",
},
}

err = adapter.ConvertK8SResource(persistentVolume, &versionedPersistentVolume)
if err != nil {
return nil, fmt.Errorf("unable to convert internal object to versioned object: %w", err)
}

return &versionedPersistentVolume, nil
}

func (adapter *KubeDockerAdapter) ListPersistentVolumes(ctx context.Context) (core.PersistentVolumeList, error) {
persistentVolumes, err := adapter.listPersistentVolumes(ctx)
if err != nil {
return core.PersistentVolumeList{}, fmt.Errorf("unable to list nodes: %w", err)
}

return persistentVolumes, nil
}

func (adapter *KubeDockerAdapter) GetPersistentVolumeTable(ctx context.Context) (*metav1.Table, error) {
persistentVolumeList, err := adapter.listPersistentVolumes(ctx)
if err != nil {
return &metav1.Table{}, fmt.Errorf("unable to list nodes: %w", err)
}

return k8s.GenerateTable(&persistentVolumeList)
}

func (adapter *KubeDockerAdapter) listPersistentVolumes(ctx context.Context) (core.PersistentVolumeList, error) {
labelFilter := filters.NewArgs()
labelFilter.Add("label", k2dtypes.PersistentVolumeLabelKey)

volumeList, err := adapter.cli.VolumeList(ctx, volume.ListOptions{Filters: labelFilter})
if err != nil {
return core.PersistentVolumeList{}, fmt.Errorf("unable to list volumes to return the output values from a Docker volume: %w", err)
}

persistentVolumes := []core.PersistentVolume{}

for _, volume := range volumeList.Volumes {
persistentVolume, err := adapter.converter.ConvertVolumeToPersistentVolume(*volume)
if err != nil {
return core.PersistentVolumeList{}, fmt.Errorf("unable to convert Docker volume to PersistentVolume: %w", err)
}

persistentVolumes = append(persistentVolumes, *persistentVolume)
}

return core.PersistentVolumeList{
TypeMeta: metav1.TypeMeta{
Kind: "PersistentVolumeList",
APIVersion: "v1",
},
Items: persistentVolumes,
}, nil
}
156 changes: 156 additions & 0 deletions internal/adapter/persistentvolumeclaim.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package adapter

import (
"context"
"encoding/json"
"fmt"

"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/volume"
"github.com/docker/docker/errdefs"
adaptererr "github.com/portainer/k2d/internal/adapter/errors"
k2dtypes "github.com/portainer/k2d/internal/adapter/types"
"github.com/portainer/k2d/internal/k8s"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/apis/core"
)

func (adapter *KubeDockerAdapter) CreatePersistentVolumeClaim(ctx context.Context, persistentVolumeClaim *corev1.PersistentVolumeClaim) error {
if persistentVolumeClaim.Labels["app.kubernetes.io/managed-by"] == "Helm" {
persistentVolumeClaimData, err := json.Marshal(persistentVolumeClaim)
if err != nil {
return fmt.Errorf("unable to marshal deployment: %w", err)
}
persistentVolumeClaim.ObjectMeta.Annotations["kubectl.kubernetes.io/last-applied-configuration"] = string(persistentVolumeClaimData)
}

_, err := adapter.cli.VolumeCreate(ctx, volume.CreateOptions{
Name: buildPersistentVolumeName(persistentVolumeClaim.Name, persistentVolumeClaim.Namespace),
Driver: "local",
Labels: map[string]string{
k2dtypes.NamespaceLabelKey: persistentVolumeClaim.Namespace,
k2dtypes.PersistentVolumeLabelKey: buildPersistentVolumeName(persistentVolumeClaim.Name, persistentVolumeClaim.Namespace),
k2dtypes.PersistentVolumeClaimLabelKey: persistentVolumeClaim.Name,
k2dtypes.PersistentVolumeClaimLastAppliedConfigLabelKey: persistentVolumeClaim.ObjectMeta.Annotations["kubectl.kubernetes.io/last-applied-configuration"],
},
})

if err != nil {
return fmt.Errorf("unable to create a Docker volume for the request persistent volume claim: %w", err)
}

return nil
}

func (adapter *KubeDockerAdapter) DeletePersistentVolumeClaim(ctx context.Context, persistentVolumeClaimName string, namespaceName string) error {
persistentVolumeName := buildPersistentVolumeName(persistentVolumeClaimName, namespaceName)

err := adapter.cli.VolumeRemove(ctx, persistentVolumeName, true)
if err != nil {
return fmt.Errorf("unable to remove Docker volume: %w", err)
}

return nil
}

func (adapter *KubeDockerAdapter) GetPersistentVolumeClaim(ctx context.Context, persistentVolumeClaimName string, namespaceName string) (*corev1.PersistentVolumeClaim, error) {
volumeName := buildPersistentVolumeName(persistentVolumeClaimName, namespaceName)
volume, err := adapter.cli.VolumeInspect(ctx, volumeName)
if err != nil {
if errdefs.IsNotFound(err) {
return nil, adaptererr.ErrResourceNotFound
}
return nil, fmt.Errorf("unable to inspect docker volume %s: %w", volumeName, err)
}

persistentVolumeClaim, err := adapter.updatePersistentVolumeClaimFromVolume(volume)
if err != nil {
return nil, fmt.Errorf("unable to update persistent volume claim from volume: %w", err)
}

versionedpersistentVolumeClaim := corev1.PersistentVolumeClaim{
TypeMeta: metav1.TypeMeta{
Kind: "PersistentVolumeClaim",
APIVersion: "v1",
},
}

err = adapter.ConvertK8SResource(persistentVolumeClaim, &versionedpersistentVolumeClaim)
if err != nil {
return nil, fmt.Errorf("unable to convert internal object to versioned object: %w", err)
}

return &versionedpersistentVolumeClaim, nil
}

func (adapter *KubeDockerAdapter) updatePersistentVolumeClaimFromVolume(volume volume.Volume) (*core.PersistentVolumeClaim, error) {
persistentVolumeClaimData := volume.Labels[k2dtypes.PersistentVolumeClaimLastAppliedConfigLabelKey]

versionedPersistentVolumeClaim := &corev1.PersistentVolumeClaim{}

err := json.Unmarshal([]byte(persistentVolumeClaimData), &versionedPersistentVolumeClaim)
if err != nil {
return nil, fmt.Errorf("unable to unmarshal versioned service: %w", err)
}

persistentVolumeClaim := core.PersistentVolumeClaim{}
err = adapter.ConvertK8SResource(versionedPersistentVolumeClaim, &persistentVolumeClaim)
if err != nil {
return nil, fmt.Errorf("unable to convert internal object to versioned object: %w", err)
}

err = adapter.converter.UpdateVolumeToPersistentVolumeClaim(&persistentVolumeClaim, volume)
if err != nil {
return nil, fmt.Errorf("unable to convert Docker volume to PersistentVolumeClaim: %w", err)
}

return &persistentVolumeClaim, nil
}

func (adapter *KubeDockerAdapter) ListPersistentVolumeClaims(ctx context.Context, namespaceName string) (core.PersistentVolumeClaimList, error) {
persistentVolumeClaims, err := adapter.listPersistentVolumeClaims(ctx, namespaceName)
if err != nil {
return core.PersistentVolumeClaimList{}, fmt.Errorf("unable to list persistent volume claims: %w", err)
}

return *persistentVolumeClaims, nil
}

func (adapter *KubeDockerAdapter) GetPersistentVolumeClaimTable(ctx context.Context, namespaceName string) (*metav1.Table, error) {
persistentVolumeClaims, err := adapter.listPersistentVolumeClaims(ctx, namespaceName)
if err != nil {
return &metav1.Table{}, fmt.Errorf("unable to list nodes: %w", err)
}

return k8s.GenerateTable(persistentVolumeClaims)
}

func (adapter *KubeDockerAdapter) listPersistentVolumeClaims(ctx context.Context, namespaceName string) (*core.PersistentVolumeClaimList, error) {
labelFilter := filters.NewArgs()
labelFilter.Add("label", k2dtypes.PersistentVolumeClaimLabelKey)
labelFilter.Add("label", fmt.Sprintf("%s=%s", k2dtypes.NamespaceLabelKey, namespaceName))

volumes, err := adapter.cli.VolumeList(ctx, volume.ListOptions{Filters: labelFilter})
if err != nil {
return &core.PersistentVolumeClaimList{}, fmt.Errorf("unable to list volumes to return the output values from a Docker volume: %w", err)
}

persistentVolumeClaims := core.PersistentVolumeClaimList{
TypeMeta: metav1.TypeMeta{
Kind: "PersistentVolumeClaimList",
APIVersion: "v1",
},
}

for _, volume := range volumes.Volumes {
persistentVolumeClaim, err := adapter.updatePersistentVolumeClaimFromVolume(*volume)
if err != nil {
return nil, fmt.Errorf("unable to update persistent volume claim from volume: %w", err)
}

persistentVolumeClaims.Items = append(persistentVolumeClaims.Items, *persistentVolumeClaim)
}

return &persistentVolumeClaims, nil
}
Loading