github.com/minio/console@v1.3.0/api/admin_config.go (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2021 MinIO, Inc. 3 // 4 // This program is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Affero 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 Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17 package api 18 19 import ( 20 "context" 21 "encoding/base64" 22 "fmt" 23 "strings" 24 25 "github.com/go-openapi/runtime/middleware" 26 "github.com/go-openapi/swag" 27 "github.com/minio/console/api/operations" 28 "github.com/minio/console/models" 29 madmin "github.com/minio/madmin-go/v3" 30 31 cfgApi "github.com/minio/console/api/operations/configuration" 32 ) 33 34 func registerConfigHandlers(api *operations.ConsoleAPI) { 35 // List Configurations 36 api.ConfigurationListConfigHandler = cfgApi.ListConfigHandlerFunc(func(params cfgApi.ListConfigParams, session *models.Principal) middleware.Responder { 37 configListResp, err := getListConfigResponse(session, params) 38 if err != nil { 39 return cfgApi.NewListConfigDefault(err.Code).WithPayload(err.APIError) 40 } 41 return cfgApi.NewListConfigOK().WithPayload(configListResp) 42 }) 43 // Configuration Info 44 api.ConfigurationConfigInfoHandler = cfgApi.ConfigInfoHandlerFunc(func(params cfgApi.ConfigInfoParams, session *models.Principal) middleware.Responder { 45 config, err := getConfigResponse(session, params) 46 if err != nil { 47 return cfgApi.NewConfigInfoDefault(err.Code).WithPayload(err.APIError) 48 } 49 return cfgApi.NewConfigInfoOK().WithPayload(config) 50 }) 51 // Set Configuration 52 api.ConfigurationSetConfigHandler = cfgApi.SetConfigHandlerFunc(func(params cfgApi.SetConfigParams, session *models.Principal) middleware.Responder { 53 resp, err := setConfigResponse(session, params) 54 if err != nil { 55 return cfgApi.NewSetConfigDefault(err.Code).WithPayload(err.APIError) 56 } 57 return cfgApi.NewSetConfigOK().WithPayload(resp) 58 }) 59 // Reset Configuration 60 api.ConfigurationResetConfigHandler = cfgApi.ResetConfigHandlerFunc(func(params cfgApi.ResetConfigParams, session *models.Principal) middleware.Responder { 61 resp, err := resetConfigResponse(session, params) 62 if err != nil { 63 return cfgApi.NewResetConfigDefault(err.Code).WithPayload(err.APIError) 64 } 65 return cfgApi.NewResetConfigOK().WithPayload(resp) 66 }) 67 // Export Configuration as base64 string. 68 api.ConfigurationExportConfigHandler = cfgApi.ExportConfigHandlerFunc(func(params cfgApi.ExportConfigParams, session *models.Principal) middleware.Responder { 69 resp, err := exportConfigResponse(session, params) 70 if err != nil { 71 return cfgApi.NewExportConfigDefault(err.Code).WithPayload(err.APIError) 72 } 73 return cfgApi.NewExportConfigOK().WithPayload(resp) 74 }) 75 api.ConfigurationPostConfigsImportHandler = cfgApi.PostConfigsImportHandlerFunc(func(params cfgApi.PostConfigsImportParams, session *models.Principal) middleware.Responder { 76 _, err := importConfigResponse(session, params) 77 if err != nil { 78 return cfgApi.NewPostConfigsImportDefault(err.Code).WithPayload(err.APIError) 79 } 80 return cfgApi.NewPostConfigsImportDefault(200) 81 }) 82 } 83 84 // listConfig gets all configurations' names and their descriptions 85 func listConfig(client MinioAdmin) ([]*models.ConfigDescription, error) { 86 ctx, cancel := context.WithCancel(context.Background()) 87 defer cancel() 88 configKeysHelp, err := client.helpConfigKV(ctx, "", "", false) 89 if err != nil { 90 return nil, err 91 } 92 var configDescs []*models.ConfigDescription 93 for _, c := range configKeysHelp.KeysHelp { 94 desc := &models.ConfigDescription{ 95 Key: c.Key, 96 Description: c.Description, 97 } 98 configDescs = append(configDescs, desc) 99 } 100 return configDescs, nil 101 } 102 103 // getListConfigResponse performs listConfig() and serializes it to the handler's output 104 func getListConfigResponse(session *models.Principal, params cfgApi.ListConfigParams) (*models.ListConfigResponse, *CodedAPIError) { 105 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 106 defer cancel() 107 mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 108 if err != nil { 109 return nil, ErrorWithContext(ctx, err) 110 } 111 // create a MinIO Admin Client interface implementation 112 // defining the client to be used 113 adminClient := AdminClient{Client: mAdmin} 114 115 configDescs, err := listConfig(adminClient) 116 if err != nil { 117 return nil, ErrorWithContext(ctx, err) 118 } 119 listGroupsResponse := &models.ListConfigResponse{ 120 Configurations: configDescs, 121 Total: int64(len(configDescs)), 122 } 123 return listGroupsResponse, nil 124 } 125 126 // getConfig gets the key values for a defined configuration. 127 func getConfig(ctx context.Context, client MinioAdmin, name string) ([]*models.Configuration, error) { 128 configBytes, err := client.getConfigKV(ctx, name) 129 if err != nil { 130 return nil, err 131 } 132 subSysConfigs, err := madmin.ParseServerConfigOutput(string(configBytes)) 133 if err != nil { 134 return nil, err 135 } 136 var configSubSysList []*models.Configuration 137 for _, scfg := range subSysConfigs { 138 if !madmin.SubSystems.Contains(scfg.SubSystem) { 139 return nil, fmt.Errorf("no sub-systems found") 140 } 141 var confkv []*models.ConfigurationKV 142 for _, kv := range scfg.KV { 143 var envOverride *models.EnvOverride 144 145 if kv.EnvOverride != nil { 146 envOverride = &models.EnvOverride{ 147 Name: kv.EnvOverride.Name, 148 Value: kv.EnvOverride.Value, 149 } 150 } 151 152 confkv = append(confkv, &models.ConfigurationKV{Key: kv.Key, Value: kv.Value, EnvOverride: envOverride}) 153 } 154 if len(confkv) == 0 { 155 continue 156 } 157 var fullConfigName string 158 if scfg.Target == "" { 159 fullConfigName = scfg.SubSystem 160 } else { 161 fullConfigName = scfg.SubSystem + ":" + scfg.Target 162 } 163 configSubSysList = append(configSubSysList, &models.Configuration{KeyValues: confkv, Name: fullConfigName}) 164 } 165 return configSubSysList, nil 166 } 167 168 // getConfigResponse performs getConfig() and serializes it to the handler's output 169 func getConfigResponse(session *models.Principal, params cfgApi.ConfigInfoParams) ([]*models.Configuration, *CodedAPIError) { 170 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 171 defer cancel() 172 mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 173 if err != nil { 174 return nil, ErrorWithContext(ctx, err) 175 } 176 // create a MinIO Admin Client interface implementation 177 // defining the client to be used 178 adminClient := AdminClient{Client: mAdmin} 179 180 configurations, err := getConfig(ctx, adminClient, params.Name) 181 if err != nil { 182 errorVal := ErrorWithContext(ctx, err) 183 minioError := madmin.ToErrorResponse(err) 184 if minioError.Code == "XMinioConfigError" { 185 errorVal.Code = 404 186 } 187 return nil, errorVal 188 } 189 return configurations, nil 190 } 191 192 // setConfig sets a configuration with the defined key values 193 func setConfig(ctx context.Context, client MinioAdmin, configName *string, kvs []*models.ConfigurationKV) (restart bool, err error) { 194 config := buildConfig(configName, kvs) 195 restart, err = client.setConfigKV(ctx, *config) 196 if err != nil { 197 return false, err 198 } 199 return restart, nil 200 } 201 202 func setConfigWithARNAccountID(ctx context.Context, client MinioAdmin, configName *string, kvs []*models.ConfigurationKV, arnAccountID string) (restart bool, err error) { 203 // if arnAccountID is not empty the configuration will be treated as a notification target 204 // arnAccountID will be used as an identifier for that specific target 205 // docs: https://min.io/docs/minio/linux/administration/monitoring/bucket-notifications.html 206 if arnAccountID != "" { 207 configName = swag.String(fmt.Sprintf("%s:%s", *configName, arnAccountID)) 208 } 209 return setConfig(ctx, client, configName, kvs) 210 } 211 212 // buildConfig builds a concatenated string including name and keyvalues 213 // e.g. `region name=us-west-1` 214 func buildConfig(configName *string, kvs []*models.ConfigurationKV) *string { 215 var builder strings.Builder 216 builder.WriteString(*configName) 217 for _, kv := range kvs { 218 key := strings.TrimSpace(kv.Key) 219 if key == "" { 220 continue 221 } 222 builder.WriteString(" ") 223 builder.WriteString(key) 224 builder.WriteString("=") 225 // All newlines must be converted to ',' 226 builder.WriteString(strings.ReplaceAll(strings.TrimSpace(fmt.Sprintf("\"%s\"", kv.Value)), "\n", ",")) 227 } 228 config := builder.String() 229 return &config 230 } 231 232 // setConfigResponse implements setConfig() to be used by handler 233 func setConfigResponse(session *models.Principal, params cfgApi.SetConfigParams) (*models.SetConfigResponse, *CodedAPIError) { 234 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 235 defer cancel() 236 237 mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 238 if err != nil { 239 return nil, ErrorWithContext(ctx, err) 240 } 241 // create a MinIO Admin Client interface implementation 242 // defining the client to be used 243 adminClient := AdminClient{Client: mAdmin} 244 configName := params.Name 245 246 needsRestart, err := setConfigWithARNAccountID(ctx, adminClient, &configName, params.Body.KeyValues, params.Body.ArnResourceID) 247 if err != nil { 248 return nil, ErrorWithContext(ctx, err) 249 } 250 return &models.SetConfigResponse{Restart: needsRestart}, nil 251 } 252 253 func resetConfig(ctx context.Context, client MinioAdmin, configName *string) (err error) { 254 err = client.delConfigKV(ctx, *configName) 255 return err 256 } 257 258 // resetConfigResponse implements resetConfig() to be used by handler 259 func resetConfigResponse(session *models.Principal, params cfgApi.ResetConfigParams) (*models.SetConfigResponse, *CodedAPIError) { 260 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 261 defer cancel() 262 263 mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 264 if err != nil { 265 return nil, ErrorWithContext(ctx, err) 266 } 267 // create a MinIO Admin Client interface implementation 268 // defining the client to be used 269 adminClient := AdminClient{Client: mAdmin} 270 271 err = resetConfig(ctx, adminClient, ¶ms.Name) 272 if err != nil { 273 return nil, ErrorWithContext(ctx, err) 274 } 275 276 return &models.SetConfigResponse{Restart: true}, nil 277 } 278 279 func exportConfigResponse(session *models.Principal, params cfgApi.ExportConfigParams) (*models.ConfigExportResponse, *CodedAPIError) { 280 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 281 defer cancel() 282 283 mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 284 if err != nil { 285 return nil, ErrorWithContext(ctx, err) 286 } 287 configRes, err := mAdmin.GetConfig(ctx) 288 if err != nil { 289 return nil, ErrorWithContext(ctx, err) 290 } 291 // may contain sensitive information so unpack only when required. 292 return &models.ConfigExportResponse{ 293 Status: "success", 294 Value: base64.StdEncoding.EncodeToString(configRes), 295 }, nil 296 } 297 298 func importConfigResponse(session *models.Principal, params cfgApi.PostConfigsImportParams) (*cfgApi.PostConfigsImportDefault, *CodedAPIError) { 299 ctx, cancel := context.WithCancel(params.HTTPRequest.Context()) 300 defer cancel() 301 mAdmin, err := NewMinioAdminClient(params.HTTPRequest.Context(), session) 302 if err != nil { 303 return nil, ErrorWithContext(ctx, err) 304 } 305 file, _, err := params.HTTPRequest.FormFile("file") 306 if err != nil { 307 return nil, ErrorWithContext(ctx, err) 308 } 309 defer file.Close() 310 311 err = mAdmin.SetConfig(ctx, file) 312 if err != nil { 313 return nil, ErrorWithContext(ctx, err) 314 } 315 return &cfgApi.PostConfigsImportDefault{}, nil 316 }