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

When using Viper, it seems difficult to easily delete an existing configuration item. #1935

Open
1 task done
LuSrackhall opened this issue Oct 7, 2024 · 1 comment
Open
1 task done
Labels
kind/enhancement New feature or request

Comments

@LuSrackhall
Copy link

Preflight Checklist

  • I have searched the issue tracker for an issue that matches the one I want to file, without success.

Problem Description

I hope there is an API that can achieve this functionality.

I also noticed that there was a related proposal in a pull request a long time ago, but it seems that after discussion, it could not be merged because it was not suitable for all scenarios of Viper.

Viper has a large user base, and there are many considerations for its applicable scenarios and breadth. However, could APIs be provided based on specific scenarios? (Just note in the documentation that this API is only applicable in certain scenarios, just like some APIs in programming behave differently or are not applicable across different operating systems, just note it.)

For example, users like me only need a simple local configuration file source and the ability to delete related configurations in the configuration file.

Proposed Solution

Classify the usage scenarios and sources of Viper, and provide APIs that are only targeted at specific classification situations, with the usage scope noted in the documentation. This could make it easier and more user-friendly for users in single scenarios to use Viper.

This might bring additional maintenance costs, but nothing is perfect. If all new features have to consider all scenarios (even if some scenarios do not care about this feature, but still have to be considered), doesn’t this introduce additional design costs

Alternatives Considered

No response

Additional Information

I am just a new user who has recently started using Viper, and my usage scenario is very simple (I am only using some of the basic functions provided by Viper).

Although I notice that Viper is very powerful, it doesn’t seem to be very user-friendly. Many parts are designed too advanced, so some functions that many users consider necessary do not have simple ways to use them.

For example, the small practical problem I am currently encountering. (However, there must be a clever solution, I just haven’t found it yet.)

Re-submitting the request is just to hope that Viper can be more user-friendly on the basis of being powerful.

@LuSrackhall
Copy link
Author

In the past day, I have conducted extensive testing and comparisons, treating Viper as a black box. The goal is to obtain a usable key-value deletion solution, without considering performance and speed.

This is my current temporary solution, which has a nearly 99.99% success rate for deleting a specific key-value pair.

It using the bug mentioned in #1934, cleverly implement the functionality to delete a specific key-value pair.

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/spf13/viper"
)

var deleteKeyValue []struct{} // 无法保证100%删除, 因为这种删除方式是利用了一些未知的黑盒bug。(tips: 千万不要完成其定义, 或者说千万不用对其进行任何形式的初始化。)

func main() {
	initViperConfig()

	viperSet("example.a", "123")
	viperSet("example.b", "123")
	viperSet("example.c", "123")
	viperSet("example.d", 123)
	viperSet("example.e", 123)
	viperSet("example.f", 123)
	viperSet("example.g", true)
	viperSet("example.h", true)
	viperSet("example.i", true)
	viperSet("example.j", false)
	viperSet("example.k", false)
	viperSet("example.l", false)
	viperSet("example.o", 3.14159)
	viperSet("example.p", 3.14159)
	viperSet("example.q", 3.14159)
	viperSet("example.r", struct{}{})
	viperSet("example.s", struct{}{})
	viperSet("example.t", struct{}{})
	viperSet("example.u", []any{})
	viperSet("example.v", []any{})
	viperSet("example.w", []any{})

	time.Sleep(time.Second * 3)

	// 删除一些key-value, 但保留   f 、 p  、 r
	viperDelete("example.a")
	viperDelete("example.b")
	viperDelete("example.c")
	viperDelete("example.d")
	viperDelete("example.e")
	// viperDelete("example.f")
	viperDelete("example.g")
	viperDelete("example.h")
	viperDelete("example.i")
	viperDelete("example.j")
	viperDelete("example.k")
	viperDelete("example.l")
	viperDelete("example.o")
	// viperDelete("example.p")
	viperDelete("example.q")
	// viperDelete("example.r")
	viperDelete("example.s")
	viperDelete("example.t")
	viperDelete("example.u")
	viperDelete("example.v")
	viperDelete("example.w")

	time.Sleep(time.Second * 3)

	// 删除 f
	viperDelete("example.f")

	time.Sleep(time.Second * 3)

	// 删除 p
	viperDelete("example.p")

	time.Sleep(time.Second * 3)

	// 删除r
	viperDelete("example.r")
	for {
	}
}

// 直接使用 viper 初始化对其应的配置文件, 供后续验证使用
func initViperConfig() {
	viper.SetConfigName("config")
	viper.SetConfigType("json")
	viper.AddConfigPath(".")
	err := viper.ReadInConfig()
	if err != nil {
		if _, ok := err.(viper.ConfigFileNotFoundError); ok {
			// 配置文件不存在,创建默认配置
			defaultConfig := map[string]interface{}{
				// 在这里添加默认配置项
				"example_key": "example_value",
			}
			for key, value := range defaultConfig {
				viper.SetDefault(key, value)
			}
			err = viper.SafeWriteConfig()
			if err != nil {
				log.Printf("创建配置文件失败: %s\n", err)
			} else {
				log.Println("已创建默认配置文件")
			}
		} else {
			log.Printf("读取配置文件失败: %s\n", err)
		}
	}
	viper.WatchConfig()
}

func viperSet(key string, value interface{}) {
	viper.Set(key, value)
	if err := viper.WriteConfig(); err != nil {
		fmt.Println("向config.json保存配置时发生致命错误", "err", err.Error())
	}
	viper.Set(key, nil)
	// 等待 viper.WatchConfig 监听真实配置
	sleep := true
	ch := make(chan (struct{}))
	defer close(ch)

	go func(sleep *bool, ch chan struct{}) {
		defer fmt.Println("保护功能完成, 退出当前goroutine以结束保护---config.json")
		for {
			select {
			case <-ch:
				fmt.Println("符合预期的退出行为---config.json")
				return
			case <-time.After(time.Millisecond * 100): // 这个最大退出时间, 由您自由指定
				fmt.Println("到达等待时间上限, 而进行的自动强制退出行为, 以避免资源浪费式的长期甚至永久等待行为---config.json")
				*sleep = false
				return
			}
		}
	}(&sleep, ch)

	for sleep {
		if viper.Get(key) != nil {
			sleep = false
			// 如果函数结束, 通道自然会关闭, 从而解除阻塞行为。无需使用下行中可能引入新阻塞的逻辑。
			// ch <- struct{}{}
		} else {
			fmt.Println("阻止了config.json中一次可能存在的错误删除行为")
		}
	}
}

func viperDelete(key string) {
	viper.Set(key, deleteKeyValue)
	if err := viper.WriteConfig(); err != nil {
		fmt.Println("删除键值对时发生致命错误", "err", err.Error())
	}
	viper.Set(key, nil)

	viper.Set(key, deleteKeyValue)
	if err := viper.WriteConfig(); err != nil {
		fmt.Println("删除键值对时发生致命错误", "err", err.Error())
	}
	viper.Set(key, nil)

	viper.Set(key, deleteKeyValue)
	if err := viper.WriteConfig(); err != nil {
		fmt.Println("删除键值对时发生致命错误", "err", err.Error())
	}
	viper.Set(key, nil)

	if err := viper.WriteConfig(); err != nil {
		fmt.Println("删除键值对时发生致命错误", "err", err.Error())
	}
	viper.Set(key, nil)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant