Skip to content

Commit

Permalink
configmap manage (#83)
Browse files Browse the repository at this point in the history
* feat: add configmap resource

Signed-off-by: warjiang <[email protected]>

* feat: add NoDataSelect & channel for get cm list

Signed-off-by: warjiang <[email protected]>

* feat: implement REST api for configmap

Signed-off-by: warjiang <[email protected]>

* chore: rm configmap list_test

Signed-off-by: dingwenjiang <[email protected]>

---------

Signed-off-by: warjiang <[email protected]>
Signed-off-by: dingwenjiang <[email protected]>
  • Loading branch information
warjiang authored Aug 15, 2024
1 parent 97a4171 commit 6f501fd
Show file tree
Hide file tree
Showing 8 changed files with 270 additions and 0 deletions.
1 change: 1 addition & 0 deletions cmd/api/app/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
// Importing route packages forces route registration
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/auth"
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/cluster"
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/configmap"
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/deployment"
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/namespace"
_ "github.com/karmada-io/dashboard/cmd/api/app/routes/overview"
Expand Down
40 changes: 40 additions & 0 deletions cmd/api/app/routes/configmap/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package service

import (
"github.com/gin-gonic/gin"
"github.com/karmada-io/dashboard/cmd/api/app/router"
"github.com/karmada-io/dashboard/cmd/api/app/types/common"
"github.com/karmada-io/dashboard/pkg/client"
"github.com/karmada-io/dashboard/pkg/resource/configmap"
)

func handleGetConfigMap(c *gin.Context) {
k8sClient := client.InClusterClientForKarmadaApiServer()
dataSelect := common.ParseDataSelectPathParameter(c)
nsQuery := common.ParseNamespacePathParameter(c)
result, err := configmap.GetConfigMapList(k8sClient, nsQuery, dataSelect)
if err != nil {
common.Fail(c, err)
return
}
common.Success(c, result)
}

func handleGetConfigMapDetail(c *gin.Context) {
k8sClient := client.InClusterClientForKarmadaApiServer()
namespace := c.Param("namespace")
name := c.Param("name")
result, err := configmap.GetConfigMapDetail(k8sClient, namespace, name)
if err != nil {
common.Fail(c, err)
return
}
common.Success(c, result)
}

func init() {
r := router.V1()
r.GET("/configmap", handleGetConfigMap)
r.GET("/configmap/:namespace", handleGetConfigMap)
r.GET("/configmap/:namespace/:name", handleGetConfigMapDetail)
}
3 changes: 3 additions & 0 deletions pkg/dataselect/dataselectquery.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ var NoFilter = &FilterQuery{
FilterByList: []FilterBy{},
}

// NoDataSelect is an option for no data select (same data will be returned).
var NoDataSelect = NewDataSelectQuery(NoPagination, NoSort, NoFilter)

// NewDataSelectQuery creates DataSelectQuery object from simpler data select queries.
func NewDataSelectQuery(paginationQuery *PaginationQuery, sortQuery *SortQuery, filterQuery *FilterQuery) *DataSelectQuery {
return &DataSelectQuery{
Expand Down
28 changes: 28 additions & 0 deletions pkg/resource/common/resourcechannels.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,34 @@ type ConfigMapListChannel struct {
Error chan error
}

// GetConfigMapListChannel returns a pair of channels to a ConfigMap list and errors that both must be read
// numReads times.
func GetConfigMapListChannel(client client.Interface, nsQuery *NamespaceQuery,
numReads int) ConfigMapListChannel {

channel := ConfigMapListChannel{
List: make(chan *v1.ConfigMapList, numReads),
Error: make(chan error, numReads),
}

go func() {
list, err := client.CoreV1().ConfigMaps(nsQuery.ToRequestParam()).List(context.TODO(), helpers.ListEverything)
var filteredItems []v1.ConfigMap
for _, item := range list.Items {
if nsQuery.Matches(item.ObjectMeta.Namespace) {
filteredItems = append(filteredItems, item)
}
}
list.Items = filteredItems
for i := 0; i < numReads; i++ {
channel.List <- list
channel.Error <- err
}
}()

return channel
}

// SecretListChannel is a list and error channels to Secrets.
type SecretListChannel struct {
List chan *v1.SecretList
Expand Down
40 changes: 40 additions & 0 deletions pkg/resource/configmap/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package configmap

import (
"github.com/karmada-io/dashboard/pkg/dataselect"
api "k8s.io/api/core/v1"
)

// The code below allows to perform complex data section on []api.ConfigMap

type ConfigMapCell api.ConfigMap

func (self ConfigMapCell) GetProperty(name dataselect.PropertyName) dataselect.ComparableValue {
switch name {
case dataselect.NameProperty:
return dataselect.StdComparableString(self.ObjectMeta.Name)
case dataselect.CreationTimestampProperty:
return dataselect.StdComparableTime(self.ObjectMeta.CreationTimestamp.Time)
case dataselect.NamespaceProperty:
return dataselect.StdComparableString(self.ObjectMeta.Namespace)
default:
// if name is not supported then just return a constant dummy value, sort will have no effect.
return nil
}
}

func toCells(std []api.ConfigMap) []dataselect.DataCell {
cells := make([]dataselect.DataCell, len(std))
for i := range std {
cells[i] = ConfigMapCell(std[i])
}
return cells
}

func fromCells(cells []dataselect.DataCell) []api.ConfigMap {
std := make([]api.ConfigMap, len(cells))
for i := range std {
std[i] = api.ConfigMap(cells[i].(ConfigMapCell))
}
return std
}
41 changes: 41 additions & 0 deletions pkg/resource/configmap/detail.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package configmap

import (
"context"
"log"

v1 "k8s.io/api/core/v1"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)

// ConfigMapDetail API resource provides mechanisms to inject containers with configuration data while keeping
// containers agnostic of Kubernetes
type ConfigMapDetail struct {
// Extends list item structure.
ConfigMap `json:",inline"`

// Data contains the configuration data.
// Each key must be a valid DNS_SUBDOMAIN with an optional leading dot.
Data map[string]string `json:"data,omitempty"`
}

// GetConfigMapDetail returns detailed information about a config map
func GetConfigMapDetail(client kubernetes.Interface, namespace, name string) (*ConfigMapDetail, error) {
log.Printf("Getting details of %s config map in %s namespace", name, namespace)

rawConfigMap, err := client.CoreV1().ConfigMaps(namespace).Get(context.TODO(), name, metaV1.GetOptions{})

if err != nil {
return nil, err
}

return getConfigMapDetail(rawConfigMap), nil
}

func getConfigMapDetail(rawConfigMap *v1.ConfigMap) *ConfigMapDetail {
return &ConfigMapDetail{
ConfigMap: toConfigMap(rawConfigMap.ObjectMeta),
Data: rawConfigMap.Data,
}
}
37 changes: 37 additions & 0 deletions pkg/resource/configmap/detail_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package configmap

import (
"github.com/karmada-io/dashboard/pkg/common/types"
"reflect"
"testing"

v1 "k8s.io/api/core/v1"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestGetConfigMapDetail(t *testing.T) {
cases := []struct {
configMaps *v1.ConfigMap
expected *ConfigMapDetail
}{
{
&v1.ConfigMap{
Data: map[string]string{"app": "my-name"}, ObjectMeta: metaV1.ObjectMeta{Name: "foo"},
},
&ConfigMapDetail{
ConfigMap: ConfigMap{
TypeMeta: types.TypeMeta{Kind: "configmap"},
ObjectMeta: types.ObjectMeta{Name: "foo"},
},
Data: map[string]string{"app": "my-name"},
},
},
}
for _, c := range cases {
actual := getConfigMapDetail(c.configMaps)
if !reflect.DeepEqual(actual, c.expected) {
t.Errorf("getConfigMapDetail(%#v) == \n%#v\nexpected \n%#v\n",
c.configMaps, actual, c.expected)
}
}
}
80 changes: 80 additions & 0 deletions pkg/resource/configmap/list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package configmap

import (
"github.com/karmada-io/dashboard/pkg/common/errors"
"github.com/karmada-io/dashboard/pkg/common/types"
"github.com/karmada-io/dashboard/pkg/dataselect"
"github.com/karmada-io/dashboard/pkg/resource/common"
"log"

v1 "k8s.io/api/core/v1"
metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
)

// ConfigMapList contains a list of Config Maps in the cluster.
type ConfigMapList struct {
ListMeta types.ListMeta `json:"listMeta"`

// Unordered list of Config Maps
Items []ConfigMap `json:"items"`

// List of non-critical errors, that occurred during resource retrieval.
Errors []error `json:"errors"`
}

// ConfigMap API resource provides mechanisms to inject containers with configuration data while keeping
// containers agnostic of Kubernetes
type ConfigMap struct {
ObjectMeta types.ObjectMeta `json:"objectMeta"`
TypeMeta types.TypeMeta `json:"typeMeta"`
}

// GetConfigMapList returns a list of all ConfigMaps in the cluster.
func GetConfigMapList(client kubernetes.Interface, nsQuery *common.NamespaceQuery, dsQuery *dataselect.DataSelectQuery) (*ConfigMapList, error) {
log.Printf("Getting list config maps in the namespace %s", nsQuery.ToRequestParam())
channels := &common.ResourceChannels{
ConfigMapList: common.GetConfigMapListChannel(client, nsQuery, 1),
}

return GetConfigMapListFromChannels(channels, dsQuery)
}

// GetConfigMapListFromChannels returns a list of all Config Maps in the cluster reading required resource list once from the channels.
func GetConfigMapListFromChannels(channels *common.ResourceChannels, dsQuery *dataselect.DataSelectQuery) (*ConfigMapList, error) {
configMaps := <-channels.ConfigMapList.List
err := <-channels.ConfigMapList.Error
nonCriticalErrors, criticalError := errors.ExtractErrors(err)
if criticalError != nil {
return nil, criticalError
}

result := toConfigMapList(configMaps.Items, nonCriticalErrors, dsQuery)

return result, nil
}

func toConfigMap(meta metaV1.ObjectMeta) ConfigMap {
return ConfigMap{
ObjectMeta: types.NewObjectMeta(meta),
TypeMeta: types.NewTypeMeta(types.ResourceKindConfigMap),
}
}

func toConfigMapList(configMaps []v1.ConfigMap, nonCriticalErrors []error, dsQuery *dataselect.DataSelectQuery) *ConfigMapList {
result := &ConfigMapList{
Items: make([]ConfigMap, 0),
ListMeta: types.ListMeta{TotalItems: len(configMaps)},
Errors: nonCriticalErrors,
}

configMapCells, filteredTotal := dataselect.GenericDataSelectWithFilter(toCells(configMaps), dsQuery)
configMaps = fromCells(configMapCells)
result.ListMeta = types.ListMeta{TotalItems: filteredTotal}

for _, item := range configMaps {
result.Items = append(result.Items, toConfigMap(item.ObjectMeta))
}

return result
}

0 comments on commit 6f501fd

Please sign in to comment.