Repository: google/go-flow-levee
Pull request #297 (2021-04-07 23:43:49)

Allow config to be set directly.
This change enables config bytes to be provided directly, instead of passing a file path on the command line. This is useful when running in an environment where file access is inconvenient.

Originally, I wanted to provide an API to set the config using a `Config` object. However, I think that this exposes too much of the implementation details of the `config` package. It is also a cumbersome change, requiring all types below `Config` to be made public and adding aliases for them in `pkg/levee`. I think it is safer and simpler to only provide an API to set the bytes. This is sufficient for the use case that prompted this change, and it allows the config to be provided in the usual format.

The tests for this API are isolated in their own file to avoid interfering with existing tests of the `config` package.

I am on the fence about whether this needs an explicit callout in the documentation. This feels like an advanced feature, which is unlikely to be relevant to most users. Let me know what you think.

- [x] Running against a large codebase such as [Kubernetes](https://github.com/kubernetes/kubernetes) does not error out. (See [DEVELOPING.md](https://github.com/google/go-flow-levee/blob/master/DEVELOPING.md) for instructions on how to do that.)
- [ ] Appropriate changes to README are included in PR

mlevesquedion: Checks are failing with a rather opaque error message ("This check failed" - that's the whole message.)

GitHub status is green.

Re-running the jobs did not help.

Leaving this as a draft for now, will try again tomorrow.
PurelyApplied: > Checks are failing with a rather opaque error message ("This check failed" - that's the whole message.)

Next to `CI`, there's a `!` message balloon.  Clicking on that, I get `GitHub Actions has encountered an internal error when running your job.`  Github status reports green, so hopefully it'll sort itself out in the near future.

Patch:
diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go
index 24ec132..dfca45d 100644
--- a/internal/pkg/config/config.go
+++ b/internal/pkg/config/config.go
@@ -33,12 +33,23 @@ var (
 	// FlagSet should be used by analyzers to reuse -config flag.
 	FlagSet    flag.FlagSet
 	configFile string
+
+	configBytes        []byte
+	configFromBytes    *Config
+	configFromBytesErr error
 )
 
 func init() {
 	FlagSet.StringVar(&configFile, "config", "config.yaml", "path to analysis configuration file")
 }
 
+// SetBytes allows the contents of a configuration file
+// to be provided directly. This is useful when running the tool
+// in an environment where file access is inconvenient.
+func SetBytes(b []byte) {
+	configBytes = b
+}
+
 // Config contains matchers and analysis scope information.
 type Config struct {
 	ReportMessage             string
@@ -295,12 +306,30 @@ func (fm funcMatcher) MatchFunction(path, receiver, name string) bool {
 	return fm.Package.MatchString(path) && fm.Receiver.MatchString(receiver) && fm.Method.MatchString(name)
 }
 
-// ReadConfig fetches configuration from the config cache.
-// The cache reads, parses, and validates config file if necessary.
+// ReadConfig reads configuration from the config cache.
+// The cache reads, parses, and validates the config file if necessary.
+// If the config bytes were set using SetConfigBytes, they are used instead.
 func ReadConfig() (*Config, error) {
+	if configBytes != nil {
+		return readConfigBytes()
+	}
+
 	return cache.read(configFile)
 }
 
+func readConfigBytes() (*Config, error) {
+	if configFromBytes == nil {
+		unmarshalled := new(Config)
+		unmarshalledErr := yaml.UnmarshalStrict(configBytes, unmarshalled)
+		if unmarshalledErr != nil {
+			fmt.Println(unmarshalledErr)
+		}
+		configFromBytes, configFromBytesErr = unmarshalled, unmarshalledErr
+	}
+
+	return configFromBytes, configFromBytesErr
+}
+
 // configCacheElement reduces disk access across multiple ReadConfig calls.
 type configCacheElement struct {
 	once       sync.Once
diff --git a/pkg/levee/levee.go b/pkg/levee/levee.go
index 4a8d198..57d91af 100644
--- a/pkg/levee/levee.go
+++ b/pkg/levee/levee.go
@@ -16,6 +16,7 @@
 package levee
 
 import (
+	"github.com/google/go-flow-levee/internal/pkg/config"
 	"github.com/google/go-flow-levee/internal/pkg/levee"
 	"github.com/google/go-flow-levee/internal/pkg/propagation/summary"
 )
@@ -23,6 +24,9 @@ import (
 // Analyzer reports instances of source data reaching a sink.
 var Analyzer = levee.Analyzer
 
+// SetBytes is a wrapper around the config package's SetBytes function.
+var SetBytes = config.SetBytes
+
 // FuncSummaries is a wrapper around the propagation/summary
 // package's map of regular function summaries.
 var FuncSummaries = summary.FuncSummaries


Test patch:
diff --git a/internal/pkg/config/set_config_bytes_test.go b/internal/pkg/config/set_config_bytes_test.go
new file mode 100644
index 0000000..22e36ee
--- /dev/null
+++ b/internal/pkg/config/set_config_bytes_test.go
@@ -0,0 +1,37 @@
+// Copyright 2020 Google LLC
+//
+// Licensed 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
+//
+// https://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/google/go-cmp/cmp"
+)
+
+func TestSetConfigBytes(t *testing.T) {
+	set := &Config{ReportMessage: "test"}
+	bytes := []byte(`ReportMessage: "test"`)
+
+	SetBytes(bytes)
+
+	read, err := ReadConfig()
+	if err != nil {
+		t.Fatalf("ReadConfig returned an unexpected error: %v", err)
+	}
+
+	if diff := cmp.Diff(set, read); diff != "" {
+		t.Errorf("set config differs from read config (-set, +read):\n%s", diff)
+	}
+}