diff --git a/cmd/iceberg/main.go b/cmd/iceberg/main.go index fb256181..7ec4a8e0 100644 --- a/cmd/iceberg/main.go +++ b/cmd/iceberg/main.go @@ -27,6 +27,7 @@ import ( "github.com/apache/iceberg-go" "github.com/apache/iceberg-go/catalog" + "github.com/apache/iceberg-go/config" "github.com/apache/iceberg-go/table" "github.com/docopt/docopt-go" ) @@ -58,7 +59,45 @@ Options: --uri TEXT specify the catalog URI --output TYPE output type (json/text) [default: text] --credential TEXT specify credentials for the catalog - --warehouse TEXT specify the warehouse to use` + --warehouse TEXT specify the warehouse to use + --config TEXT specify the path to the configuration file` + +type Config struct { + List bool `docopt:"list"` + Describe bool `docopt:"describe"` + Schema bool `docopt:"schema"` + Spec bool `docopt:"spec"` + Uuid bool `docopt:"uuid"` + Location bool `docopt:"location"` + Props bool `docopt:"properties"` + Drop bool `docopt:"drop"` + Files bool `docopt:"files"` + Rename bool `docopt:"rename"` + + Get bool `docopt:"get"` + Set bool `docopt:"set"` + Remove bool `docopt:"remove"` + + Namespace bool `docopt:"namespace"` + Table bool `docopt:"table"` + + RenameFrom string `docopt:""` + RenameTo string `docopt:""` + + Parent string `docopt:"PARENT"` + Ident string `docopt:"IDENTIFIER"` + TableID string `docopt:"TABLE_ID"` + PropName string `docopt:"PROPNAME"` + Value string `docopt:"VALUE"` + + Catalog string `docopt:"--catalog"` + URI string `docopt:"--uri"` + Output string `docopt:"--output"` + History bool `docopt:"--history"` + Cred string `docopt:"--credential"` + Warehouse string `docopt:"--warehouse"` + Config string `docopt:"--config"` +} func main() { args, err := docopt.ParseArgs(usage, os.Args[1:], iceberg.Version()) @@ -66,46 +105,17 @@ func main() { log.Fatal(err) } - cfg := struct { - List bool `docopt:"list"` - Describe bool `docopt:"describe"` - Schema bool `docopt:"schema"` - Spec bool `docopt:"spec"` - Uuid bool `docopt:"uuid"` - Location bool `docopt:"location"` - Props bool `docopt:"properties"` - Drop bool `docopt:"drop"` - Files bool `docopt:"files"` - Rename bool `docopt:"rename"` - - Get bool `docopt:"get"` - Set bool `docopt:"set"` - Remove bool `docopt:"remove"` - - Namespace bool `docopt:"namespace"` - Table bool `docopt:"table"` - - RenameFrom string `docopt:""` - RenameTo string `docopt:""` - - Parent string `docopt:"PARENT"` - Ident string `docopt:"IDENTIFIER"` - TableID string `docopt:"TABLE_ID"` - PropName string `docopt:"PROPNAME"` - Value string `docopt:"VALUE"` - - Catalog string `docopt:"--catalog"` - URI string `docopt:"--uri"` - Output string `docopt:"--output"` - History bool `docopt:"--history"` - Cred string `docopt:"--credential"` - Warehouse string `docopt:"--warehouse"` - }{} + cfg := Config{} if err := args.Bind(&cfg); err != nil { log.Fatal(err) } + fileCfg := config.ParseConfig(config.LoadConfig(cfg.Config), "default") + if fileCfg != nil { + mergeConf(fileCfg, &cfg) + } + var output Output switch strings.ToLower(cfg.Output) { case "text": @@ -341,3 +351,21 @@ func properties(output Output, cat catalog.Catalog, args propCmd) { } } } + +func mergeConf(fileConf *config.CatalogConfig, resConfig *Config) { + if len(resConfig.Catalog) == 0 { + resConfig.Catalog = fileConf.Catalog + } + if len(resConfig.URI) == 0 { + resConfig.URI = fileConf.URI + } + if len(resConfig.Output) == 0 { + resConfig.Output = fileConf.Output + } + if len(resConfig.Cred) == 0 { + resConfig.Cred = fileConf.Credential + } + if len(resConfig.Warehouse) == 0 { + resConfig.Warehouse = fileConf.Warehouse + } +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 00000000..4af7654d --- /dev/null +++ b/config/config.go @@ -0,0 +1,71 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package config + +import ( + "os" + "path/filepath" + + "gopkg.in/yaml.v3" +) + +const cfgFile = ".iceberg-go.yaml" + +type Config struct { + Catalogs map[string]CatalogConfig `yaml:"catalog"` +} + +type CatalogConfig struct { + Catalog string `yaml:"catalog"` + URI string `yaml:"uri"` + Output string `yaml:"output"` + Credential string `yaml:"credential"` + Warehouse string `yaml:"warehouse"` +} + +func LoadConfig(configPath string) []byte { + var path string + if len(configPath) > 0 { + path = configPath + } else { + homeDir, err := os.UserHomeDir() + if err != nil { + return nil + + } + path = filepath.Join(homeDir, cfgFile) + } + file, err := os.ReadFile(path) + if err != nil { + return nil + } + return file +} + +func ParseConfig(file []byte, catalogName string) *CatalogConfig { + var config Config + err := yaml.Unmarshal(file, &config) + if err != nil { + return nil + } + res, ok := config.Catalogs[catalogName] + if !ok { + return nil + } + return &res +} diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 00000000..3e177d58 --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,85 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var testArgs = []struct { + file []byte + catName string + expected *CatalogConfig +}{ + // config file does not exist + {nil, "default", nil}, + // config does not have default catalog + {[]byte(` +catalog: + custom-catalog: + catalog: rest + uri: http://localhost:8181/ + output: text + credential: client-id:client-secret + warehouse: catalog_name +`), "default", nil}, + // default catalog + {[]byte(` +catalog: + default: + catalog: rest + uri: http://localhost:8181/ + output: text + credential: client-id:client-secret + warehouse: catalog_name +`), "default", + &CatalogConfig{ + Catalog: "rest", + URI: "http://localhost:8181/", + Output: "text", + Credential: "client-id:client-secret", + Warehouse: "catalog_name", + }}, + // custom catalog + {[]byte(` +catalog: + custom-catalog: + catalog: rest + uri: http://localhost:8181/ + output: text + credential: client-id:client-secret + warehouse: catalog_name +`), "custom-catalog", + &CatalogConfig{ + Catalog: "rest", + URI: "http://localhost:8181/", + Output: "text", + Credential: "client-id:client-secret", + Warehouse: "catalog_name", + }}, +} + +func TestParseConfig(t *testing.T) { + for _, tt := range testArgs { + actual := ParseConfig([]byte(tt.file), tt.catName) + + assert.Equal(t, tt.expected, actual) + } +} diff --git a/go.mod b/go.mod index 453158e7..baff29bf 100644 --- a/go.mod +++ b/go.mod @@ -35,6 +35,7 @@ require ( github.com/twmb/murmur3 v1.1.8 github.com/wolfeidau/s3iofs v1.5.2 golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -87,5 +88,4 @@ require ( golang.org/x/text v0.18.0 // indirect golang.org/x/tools v0.25.0 // indirect golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect )