github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/tequilapi/endpoints/config.go (about)

     1  /*
     2   * Copyright (C) 2019 The "MysteriumNetwork/node" Authors.
     3   *
     4   * This program is free software: you can redistribute it and/or modify
     5   * it under the terms of the GNU General Public License as published by
     6   * the Free Software Foundation, either version 3 of the License, or
     7   * (at your option) any later version.
     8   *
     9   * This program is distributed in the hope that it will be useful,
    10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12   * GNU General Public License for more details.
    13   *
    14   * You should have received a copy of the GNU General Public License
    15   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16   */
    17  
    18  package endpoints
    19  
    20  import (
    21  	"encoding/json"
    22  	"reflect"
    23  
    24  	"github.com/gin-gonic/gin"
    25  	"github.com/mysteriumnetwork/go-rest/apierror"
    26  	"github.com/mysteriumnetwork/node/tequilapi/contract"
    27  
    28  	"github.com/mysteriumnetwork/node/config"
    29  	"github.com/mysteriumnetwork/node/tequilapi/utils"
    30  	"github.com/rs/zerolog/log"
    31  )
    32  
    33  type configProvider interface {
    34  	GetConfig() map[string]interface{}
    35  	GetDefaultConfig() map[string]interface{}
    36  	GetUserConfig() map[string]interface{}
    37  	SetUser(key string, value interface{})
    38  	RemoveUser(key string)
    39  	SaveUserConfig() error
    40  }
    41  
    42  // swagger:model configPayload
    43  type configPayload struct {
    44  	// example: {"data":{"access-policy":{"list":"mysterium"},"openvpn":{"port":5522}}}
    45  	Data map[string]interface{} `json:"data"`
    46  }
    47  
    48  type configAPI struct {
    49  	config configProvider
    50  }
    51  
    52  func newConfigAPI(config configProvider) *configAPI {
    53  	return &configAPI{config: config}
    54  }
    55  
    56  // GetConfig returns current configuration
    57  // swagger:operation GET /config Configuration getConfig
    58  //
    59  //	---
    60  //	summary: Returns current configuration values
    61  //	description: Returns default configuration
    62  //	responses:
    63  //	  200:
    64  //	    description: Currently active configuration
    65  //	    schema:
    66  //	      "$ref": "#/definitions/configPayload"
    67  func (api *configAPI) GetConfig(c *gin.Context) {
    68  	res := configPayload{Data: api.config.GetConfig()}
    69  	utils.WriteAsJSON(res, c.Writer)
    70  }
    71  
    72  // GetDefaultConfig returns default configuration
    73  // swagger:operation GET /config/default Configuration getDefaultConfig
    74  //
    75  //	---
    76  //	summary: Returns default configuration
    77  //	description: Returns default configuration
    78  //	responses:
    79  //	  200:
    80  //	    description: Default configuration values
    81  //	    schema:
    82  //	      "$ref": "#/definitions/configPayload"
    83  func (api *configAPI) GetDefaultConfig(c *gin.Context) {
    84  	res := configPayload{Data: api.config.GetDefaultConfig()}
    85  	utils.WriteAsJSON(res, c.Writer)
    86  }
    87  
    88  // GetUiFeatures returns config.ui.features value
    89  // swagger:operation GET /config/ui/features
    90  //
    91  //	---
    92  //	summary: Returns returns config.ui.features value
    93  //	description: Returns returns config.ui.features value
    94  //	responses:
    95  //	  200:
    96  //	    description: Default configuration values
    97  //	    schema:
    98  //	      type: string
    99  func (api *configAPI) GetUiFeatures(c *gin.Context) {
   100  	res := ""
   101  	cfg := api.config.GetConfig()
   102  	ui, ok := cfg["ui"].((map[string]interface{}))
   103  	if ok {
   104  		res_, ok := ui["features"]
   105  		if ok {
   106  			res = res_.(string)
   107  		}
   108  	}
   109  	c.Writer.Write([]byte(res))
   110  }
   111  
   112  // GetUserConfig returns current user configuration
   113  // swagger:operation GET /config/user Configuration getUserConfig
   114  //
   115  //	---
   116  //	summary: Returns current user configuration
   117  //	description: Returns current user configuration
   118  //	responses:
   119  //	  200:
   120  //	    description: User set configuration values
   121  //	    schema:
   122  //	      "$ref": "#/definitions/configPayload"
   123  func (api *configAPI) GetUserConfig(c *gin.Context) {
   124  	res := configPayload{Data: api.config.GetUserConfig()}
   125  	utils.WriteAsJSON(res, c.Writer)
   126  }
   127  
   128  // SetUserConfig sets and returns current configuration
   129  // swagger:operation POST /config/user Configuration serUserConfig
   130  //
   131  //	---
   132  //	summary: Sets and returns user configuration
   133  //	description: For keys present in the payload, it will set or remove the user config values (if the key is null). Changes are persisted to the config file.
   134  //	parameters:
   135  //	  - in: body
   136  //	    name: body
   137  //	    description: configuration keys/values
   138  //	    schema:
   139  //	      $ref: "#/definitions/configPayload"
   140  //	responses:
   141  //	  200:
   142  //	    description: User configuration
   143  //	    schema:
   144  //	      "$ref": "#/definitions/configPayload"
   145  //	  400:
   146  //	    description: Failed to parse or request validation failed
   147  //	    schema:
   148  //	      "$ref": "#/definitions/APIError"
   149  //	  500:
   150  //	    description: Internal server error
   151  //	    schema:
   152  //	      "$ref": "#/definitions/APIError"
   153  func (api *configAPI) SetUserConfig(c *gin.Context) {
   154  	var req configPayload
   155  	err := json.NewDecoder(c.Request.Body).Decode(&req)
   156  	if err != nil {
   157  		c.Error(apierror.ParseFailed())
   158  		return
   159  	}
   160  	for k, v := range req.Data {
   161  		if isNil(v) {
   162  			log.Debug().Msgf("Clearing user config value: %q", v)
   163  			api.config.RemoveUser(k)
   164  		} else {
   165  			log.Debug().Msgf("Setting user config value: %q = %q", k, v)
   166  			api.config.SetUser(k, v)
   167  		}
   168  	}
   169  	err = api.config.SaveUserConfig()
   170  	if err != nil {
   171  		c.Error(apierror.Internal("Failed to save config", contract.ErrCodeConfigSave))
   172  		return
   173  	}
   174  	api.GetUserConfig(c)
   175  }
   176  
   177  func isNil(val interface{}) bool {
   178  	if val == nil {
   179  		return true
   180  	}
   181  	v := reflect.ValueOf(val)
   182  	if v.Kind() == reflect.Ptr && v.IsNil() {
   183  		return true
   184  	}
   185  	return false
   186  }
   187  
   188  // AddRoutesForConfig registers /config endpoints in Tequilapi
   189  func AddRoutesForConfig(
   190  	e *gin.Engine,
   191  ) error {
   192  	api := newConfigAPI(config.Current)
   193  	g := e.Group("/config")
   194  	{
   195  		g.GET("", api.GetConfig)
   196  		g.GET("/default", api.GetDefaultConfig)
   197  		g.GET("/user", api.GetUserConfig)
   198  		g.POST("/user", api.SetUserConfig)
   199  		g.GET("/ui/features", api.GetUiFeatures)
   200  	}
   201  	return nil
   202  }