Skip to content

Commit 5f7791f

Browse files
committed
WIP - backend: Fix rename cluster issue for being able to rename a cluster to a name already in use on that config file
works: - the config path for renaming the config now uses a the accurate source path on user machines for backend logic (should work for multi config env) - the rename backend prevents renaming if the custom name from the request is already within the config file (either as a context name or a custom name) - add some UI to the frontend that responds when the isunique checker returns the http error Signed-off-by: Vincent T <[email protected]>
1 parent 3c4783a commit 5f7791f

File tree

9 files changed

+334
-60
lines changed

9 files changed

+334
-60
lines changed

backend/cmd/headlamp.go

+122-25
Original file line numberDiff line numberDiff line change
@@ -1195,11 +1195,7 @@ func (c *HeadlampConfig) getClusters() []Cluster {
11951195

11961196
source := context.SourceStr()
11971197

1198-
// find the file name from the kubeconfig path
1199-
fileName := filepath.Base(kubeconfigPath)
1200-
1201-
// create the pathID string using a pipe as the delimiter
1202-
pathID := fmt.Sprintf("%s:%s:%s", context.SourceStr(), fileName, context.Name)
1198+
pathID := context.PathID
12031199

12041200
clusters = append(clusters, Cluster{
12051201
Name: context.Name,
@@ -1512,7 +1508,8 @@ func (c *HeadlampConfig) deleteCluster(w http.ResponseWriter, r *http.Request) {
15121508
c.getConfig(w, r)
15131509
}
15141510

1515-
// Get path of kubeconfig from source.
1511+
// Get path of kubeconfig weload headlamp with from source.
1512+
// Note: This will always give the kubeconfig we loaded headlamp with, but if we have more than one, it's not clear this will yield the correct value.
15161513
func (c *HeadlampConfig) getKubeConfigPath(source string) (string, error) {
15171514
if source == "kubeconfig" {
15181515
return c.kubeConfigPath, nil
@@ -1521,6 +1518,8 @@ func (c *HeadlampConfig) getKubeConfigPath(source string) (string, error) {
15211518
return defaultKubeConfigPersistenceFile()
15221519
}
15231520

1521+
1522+
15241523
// Handler for renaming a stateless cluster.
15251524
func (c *HeadlampConfig) handleStatelessClusterRename(w http.ResponseWriter, r *http.Request, clusterName string) {
15261525
if err := c.kubeConfigStore.RemoveContext(clusterName); err != nil {
@@ -1602,7 +1601,25 @@ func (c *HeadlampConfig) updateCustomContextToCache(config *api.Config, clusterN
16021601
}
16031602

16041603
// getPathAndLoadKubeconfig gets the path of the kubeconfig file and loads it.
1605-
func (c *HeadlampConfig) getPathAndLoadKubeconfig(source, clusterName string) (string, *api.Config, error) {
1604+
func (c *HeadlampConfig) getPathAndLoadKubeconfig(source, clusterName string, loadPath string) (string, *api.Config, error) {
1605+
1606+
var usePath string
1607+
var kubeConfig *api.Config
1608+
1609+
if source == "kubeconfig" {
1610+
// Load kubeconfig file
1611+
config, err := clientcmd.LoadFromFile(loadPath)
1612+
if err != nil {
1613+
logger.Log(logger.LevelError, map[string]string{"cluster": clusterName},
1614+
err, "loading kubeconfig file")
1615+
1616+
return "", nil, err
1617+
}
1618+
1619+
usePath = loadPath
1620+
kubeConfig = config
1621+
1622+
} else {
16061623
// Get path of kubeconfig from source
16071624
path, err := c.getKubeConfigPath(source)
16081625
if err != nil {
@@ -1621,13 +1638,20 @@ func (c *HeadlampConfig) getPathAndLoadKubeconfig(source, clusterName string) (s
16211638
return "", nil, err
16221639
}
16231640

1624-
return path, config, nil
1641+
usePath = path
1642+
kubeConfig = config
1643+
}
1644+
1645+
return usePath, kubeConfig, nil
16251646
}
16261647

16271648
// Handler for renaming a cluster.
16281649
func (c *HeadlampConfig) renameCluster(w http.ResponseWriter, r *http.Request) {
16291650
vars := mux.Vars(r)
16301651
clusterName := vars["name"]
1652+
parts := strings.SplitN(r.URL.Query().Get("clusterPathID"), ":", 2)
1653+
configPath := parts[0]
1654+
16311655
// Parse request body.
16321656
var reqBody RenameClusterRequest
16331657
if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil {
@@ -1637,55 +1661,128 @@ func (c *HeadlampConfig) renameCluster(w http.ResponseWriter, r *http.Request) {
16371661

16381662
return
16391663
}
1640-
1664+
16411665
if reqBody.Stateless {
16421666
// For stateless clusters we just need to remove cluster from cache
16431667
c.handleStatelessClusterRename(w, r, clusterName)
1644-
1668+
16451669
return
16461670
}
1671+
1672+
var loadPath string
1673+
if reqBody.Source == "kubeconfig" {
1674+
loadPath = configPath
1675+
}
16471676

16481677
// Get path of kubeconfig from source
1649-
path, config, err := c.getPathAndLoadKubeconfig(reqBody.Source, clusterName)
1678+
path, config, err := c.getPathAndLoadKubeconfig(reqBody.Source, clusterName, loadPath)
16501679
if err != nil {
16511680
http.Error(w, "getting kubeconfig file", http.StatusInternalServerError)
16521681
return
16531682
}
16541683

1655-
// Find the context with the given cluster name
1684+
isUnique := checkUniqueName(config, clusterName, reqBody.NewClusterName)
1685+
if !isUnique {
1686+
http.Error(w, "custom name already in use", http.StatusBadRequest)
1687+
logger.Log(logger.LevelError, map[string]string{"cluster": clusterName},
1688+
err, "cluster name already exists in the kubeconfig")
1689+
1690+
return
1691+
}
1692+
1693+
contextName := findMatchingContextName(config, clusterName)
1694+
1695+
if err := customNameToExtenstions(config, contextName, reqBody.NewClusterName, path); err != nil {
1696+
http.Error(w, "writing custom extension to kubeconfig", http.StatusInternalServerError)
1697+
return
1698+
}
1699+
1700+
if errs := c.updateCustomContextToCache(config, clusterName); len(errs) > 0 {
1701+
http.Error(w, "setting up contexts from kubeconfig", http.StatusBadRequest)
1702+
return
1703+
}
1704+
1705+
w.WriteHeader(http.StatusCreated)
1706+
c.getConfig(w, r)
1707+
}
1708+
1709+
// findMatchingContextName checks all contexts, returning the key for whichever
1710+
// has a matching customObj.CustomName, if any.
1711+
func findMatchingContextName(config *api.Config, clusterName string) string {
16561712
contextName := clusterName
16571713

1658-
// Iterate over the contexts to find the context with the given cluster name
16591714
for k, v := range config.Contexts {
16601715
info := v.Extensions["headlamp_info"]
16611716
if info != nil {
16621717
customObj, err := MarshalCustomObject(info, contextName)
16631718
if err != nil {
16641719
logger.Log(logger.LevelError, map[string]string{"cluster": contextName},
16651720
err, "marshaling custom object")
1666-
1667-
return
1721+
continue
16681722
}
16691723

1670-
// Check if the CustomName field matches the cluster name
1671-
if customObj.CustomName != "" && customObj.CustomName == clusterName {
1724+
if customObj.CustomName == clusterName && customObj.CustomName != "" {
16721725
contextName = k
16731726
}
16741727
}
16751728
}
16761729

1677-
if err := customNameToExtenstions(config, contextName, reqBody.NewClusterName, path); err != nil {
1678-
http.Error(w, "writing custom extension to kubeconfig", http.StatusInternalServerError)
1679-
return
1730+
return contextName
1731+
}
1732+
1733+
// checkUniqueName returns false if 'newName' is already in 'names';
1734+
// otherwise returns true.
1735+
func checkUniqueName(config *api.Config, contextName string, newName string) bool {
1736+
contextNames := make([]string, 0, len(config.Contexts))
1737+
1738+
for name := range config.Contexts {
1739+
contextNames = append(contextNames, name)
1740+
logger.Log(logger.LevelInfo, map[string]string{"cluster added": name},
1741+
nil, "context name")
16801742
}
16811743

1682-
if errs := c.updateCustomContextToCache(config, clusterName); len(errs) > 0 {
1683-
http.Error(w, "setting up contexts from kubeconfig", http.StatusBadRequest)
1684-
return
1744+
// Iterate over the contexts and add the custom names
1745+
for _, y := range config.Contexts {
1746+
info := y.Extensions["headlamp_info"]
1747+
if info != nil {
1748+
customObj, err := MarshalCustomObject(info, contextName)
1749+
if err != nil {
1750+
logger.Log(logger.LevelError, map[string]string{"cluster": contextName},
1751+
err, "marshaling custom object")
1752+
}
1753+
1754+
// add custom name if it is not empty
1755+
if customObj.CustomName != "" {
1756+
contextNames = append(contextNames, customObj.CustomName)
1757+
}
1758+
}
16851759
}
16861760

1687-
w.WriteHeader(http.StatusCreated)
1688-
c.getConfig(w, r)
1761+
for _, current := range contextNames {
1762+
logger.Log(
1763+
logger.LevelInfo,
1764+
map[string]string{
1765+
"message": fmt.Sprintf("NOW COMPARING ******** %s to %s", current, newName),
1766+
},
1767+
nil,
1768+
"comparing cluster names",
1769+
)
1770+
1771+
if current == newName {
1772+
logger.Log(
1773+
logger.LevelInfo,
1774+
map[string]string{
1775+
"message": fmt.Sprintf("DUPLICATE NAME **********: %s", current),
1776+
},
1777+
nil,
1778+
"cluster name already in use",
1779+
)
1780+
1781+
return false
1782+
}
1783+
}
1784+
1785+
return true
16891786
}
16901787

16911788
func (c *HeadlampConfig) addClusterSetupRoute(r *mux.Router) {

backend/cmd/headlamp_test.go

+1-9
Original file line numberDiff line numberDiff line change
@@ -663,20 +663,12 @@ func TestRenameCluster(t *testing.T) {
663663
assert.Equal(t, http.StatusCreated, r.Code)
664664
assert.Equal(t, 2, len(clusters))
665665

666+
// to do: fix for non dynamic clusters
666667
tests := []struct {
667668
name string
668669
clusterReq RenameClusterRequest
669670
expectedState int
670671
}{
671-
{
672-
name: "passStatefull",
673-
clusterReq: RenameClusterRequest{
674-
NewClusterName: "minikubetestworks",
675-
Stateless: false,
676-
Source: "kubeconfig",
677-
},
678-
expectedState: http.StatusCreated,
679-
},
680672
{
681673
name: "stateless",
682674
clusterReq: RenameClusterRequest{

backend/pkg/kubeconfig/kubeconfig.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ type Context struct {
4646
Internal bool `json:"internal"`
4747
Error string `json:"error"`
4848
PathName string `json:"pathName"`
49+
PathID string `json:"pathID"`
4950
}
5051

5152
type OidcConfig struct {
@@ -353,7 +354,13 @@ func LoadContextsFromFile(kubeConfigPath string, source int) ([]Context, []Conte
353354

354355
// add the PathName to each context
355356
for i := range contexts {
356-
contexts[i].PathName = kubeConfigPath
357+
pathName := kubeConfigPath
358+
359+
contexts[i].PathName = pathName
360+
361+
// create the pathID string using a pipe as the delimiter
362+
pathID := fmt.Sprintf("%s:%s", pathName, contexts[i].Name)
363+
contexts[i].PathID = pathID
357364
}
358365

359366
return contexts, contextErrors, nil

frontend/src/components/App/Home/index.tsx

+36
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ function HomeComponent(props: HomeComponentProps) {
230230
}, [customNameClusters]);
231231

232232
function getClusterNames() {
233+
console.log('$$$$$$$$$$', clusters);
233234
return Object.values(clusters)
234235
.map(c => ({
235236
...c,
@@ -245,6 +246,7 @@ function HomeComponent(props: HomeComponentProps) {
245246
* @returns A description of where the cluster is picked up from: dynamic, in-cluster, or from a kubeconfig file.
246247
*/
247248
function getOrigin(cluster: Cluster): string {
249+
console.log('origin cluster: ', cluster);
248250
if (cluster.meta_data?.source === 'kubeconfig') {
249251
const sourcePath = cluster.meta_data?.origin?.kubeconfig;
250252
return `Kubeconfig: ${sourcePath}`;
@@ -256,6 +258,34 @@ function HomeComponent(props: HomeComponentProps) {
256258
return 'Unknown';
257259
}
258260

261+
function getDisplayName(cluster: Cluster) {
262+
console.log('other cluster: ', cluster);
263+
264+
if (cluster.meta_data?.extensions?.headlamp_info?.customName) {
265+
return cluster.meta_data.extensions.headlamp_info.customName + '#';
266+
} else {
267+
return cluster.name;
268+
}
269+
}
270+
271+
function ClusterName(cluster: any) {
272+
console.log('READ CLUSTER: ', cluster);
273+
274+
if (!cluster) {
275+
return null;
276+
}
277+
278+
// const isRenamed = !!cluster.meta_data?.extensions?.headlamp_info?.customName;
279+
// const displayName = cluster.meta_data?.extensions?.headlamp_info?.customName || cluster.name;
280+
// const pathID = cluster.meta_data?.origin?.kubeconfig || cluster.name;
281+
282+
// return (
283+
// <Link routeName="cluster" params={{ cluster: cluster.name }}>
284+
// {displayName} - {pathID} {isRenamed ? '(renamed)' : ''}
285+
// </Link>
286+
// )
287+
}
288+
259289
const memoizedComponent = React.useMemo(
260290
() => (
261291
<PageGrid>
@@ -284,6 +314,12 @@ function HomeComponent(props: HomeComponentProps) {
284314
</Link>
285315
),
286316
},
317+
{
318+
id: 'name',
319+
label: t('Name'),
320+
getValue: cluster => getDisplayName(cluster),
321+
render: ({ cluster }) => <ClusterName cluster={cluster} />,
322+
},
287323
{
288324
label: t('Origin'),
289325
getValue: cluster => getOrigin(cluster),

0 commit comments

Comments
 (0)