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

TerraformConfig: Add KeyValues method #506

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

thisisommore
Copy link

Work in progress PR to fix #377

@hashicorp-cla
Copy link

hashicorp-cla commented Nov 13, 2021

CLA assistant check
All committers have signed the CLA.

@thisisommore
Copy link
Author

@wilkermichael Is this the way we should go over all the structs?

@wilkermichael
Copy link
Contributor

Hey @thisisommore, thanks for taking a look at this! This solution definitely is an improvement towards querying and computer parsing.

After doing some investigation myself, I realised that the log library we’re using supports JSON output which would be the ideal format for querying tools. Therefore, it makes more sense to make sure the JSON is queryable, and the focus of the human-readable output should be on clarity. For example, I've written a test case that demos what that might look like with your change:

func TestInterface(t *testing.T) {
	tc := TerraformConfig{
		Version:    String("test"),
		Log:        Bool(true),
		WorkingDir: String("test"),
	}
	var cc ConsulConfig
	cc.Finalize()
	tc.Finalize(&cc)

        // Output human readable
	logging.Global().Info("test", tc.KeyValues()...)

	logger := hclog.New(&hclog.LoggerOptions{
		JSONFormat: true, // in the future support a flag to toggle this JSON format
		TimeFormat: hclog.TimeFormat,
	})
	hclog.SetDefault(logger)
        
        // Output JSON
	logging.Global().Info("test", tc.KeyValues()...)
}

Which outputs the human readable:

2021-11-16T11:58:24.640-0800 [INFO] test: Version=test Log=true PersistLog=false Path=/Users/mwilkerson/dev/consul-terraform-sync/config WorkingDir=test Backend="map[consul:map[address:localhost:8500 gzip:true path:consul-terraform-sync/terraform]]" RequiredProviders=map[]

and the json

{
   "@level":"info",
   "@message":"test",
   "@timestamp":"2021-11-16T11:49:13.914-0800",
   "Backend":{
      "consul":{
         "address":"localhost:8500",
         "gzip":true,
         "path":"consul-terraform-sync/terraform"
      }
   },
   "Log":true,
   "Path":"/Users/mwilkerson/dev/consul-terraform-sync/config",
   "PersistLog":false,
   "RequiredProviders":{
      
   },
   "Version":"test",
   "WorkingDir":"test"
}

One limitation with the solution here is that the JSON object doesn't include any reference to the original object, in this case TerraformConfig, so a user wouldn't be able to query for example terraformConfig.Version="test".

After investigating a bit more, I’ve added a possible alternative solution, more information and examples can now be found in the issue details. Let me know how you feel about them. I know it's a lot of information at once, so don’t hesitate to reach out if you have more questions.

@thisisommore
Copy link
Author

@wilkermichael Sorry for the late response, was going through a lot of study work, and now I am looking at this.

@thisisommore
Copy link
Author

thisisommore commented Dec 16, 2021

Hey @wilkermichael
I have a few questions

  • What can be done here?
    Should this field be omitted in json using json:"-"

    type CatalogServicesConditionConfig struct {
    CatalogServicesMonitorConfig `mapstructure:",squash"`
    }

  • Some structs are implementing this interface which has GoString so I cannot rename GoString method to String of these structs, should we rename the method in MonitorConfig interface too?

    type MonitorConfig interface {
    Copy() MonitorConfig
    Merge(MonitorConfig) MonitorConfig
    Finalize([]string)
    Validate() error
    GoString() string
    }

@wilkermichael
Copy link
Contributor

wilkermichael commented Dec 20, 2021

Hey @wilkermichael I have a few questions

  • What can be done here?
    Should this field be omitted in json using json:"-"
    type CatalogServicesConditionConfig struct {
    CatalogServicesMonitorConfig `mapstructure:",squash"`
    }
  • Some structs are implementing this interface which has GoString so I cannot rename GoString method to String of these structs, should we rename the method in MonitorConfig interface too?
    type MonitorConfig interface {
    Copy() MonitorConfig
    Merge(MonitorConfig) MonitorConfig
    Finalize([]string)
    Validate() error
    GoString() string
    }

Hey @thisisommore, thanks for your patience while I got back to you. I'll address each question here:

(1) I just did a quick test to verify the current default behaviour of that squashed field when logging json:

c := &CatalogServicesConditionConfig{
				CatalogServicesMonitorConfig{
					Regexp:            String(".*"),
					SourceIncludesVar: Bool(true),
					Datacenter:        String("dc2"),
					Namespace:         String("ns2"),
					NodeMeta: map[string]string{
						"key1": "value1",
						"key2": "value2",
					},
				},
			}

// JSON logging enabled
logging.Global().Info("this is a json log", "catalogServicesConditionConfig", c)`

yields the following log:

{
    "@level": "info",
    "@message": "this is a json log",
    "@timestamp": "2021-12-20T11:31:26.262-0800",
    "catalogServicesConditionConfig": {
        "Regexp": ".*",
        "SourceIncludesVar": true,
        "Datacenter": "dc2",
        "Namespace": "ns2",
        "NodeMeta": {
            "key1": "value1",
            "key2": "value2"
        }
    }
}

Ignoring the field names since I didn't setup struct tags for them, the general structure of this json makes sense to me. It looks like it doesn't include the squashed field name in the JSON which I think is good. If you add the json:"-" tag I believe the struct won't be included at all. I therefore suggest you not add json tags to squashed fields like in the example you provided.

(2) I think renaming the interface method to String() makes sense!

@thisisommore
Copy link
Author

@wilkermichael Thanks for addressing this, I have committed the changes, and now will try creating tests.
You can review my changes and let me know if any change is needed.

@thisisommore
Copy link
Author

@wilkermichael do I have a write the test for each struct?

@wilkermichael
Copy link
Contributor

@wilkermichael do I have a write the test for each struct?

@thisisommore thanks for your patience, I've been on holidays the last couple of weeks. I think it would be a good idea to have a test for each struct, however, you can just test whether or not the object marshal's successfully. You don't need to check that the string matches for each case.

@wilkermichael
Copy link
Contributor

@wilkermichael Thanks for addressing this, I have committed the changes, and now will try creating tests. You can review my changes and let me know if any change is needed.

@thisisommore I was looking through again and noticed you asked me to do a preliminary review. I'm happy to do one for you, however, can you please rebase your changes onto main first and resolve the conflicts? There have been a number of changes to the config since work was started on this PR.

@thisisommore thisisommore marked this pull request as ready for review January 20, 2022 15:49
@thisisommore thisisommore requested review from a team and lornasong and removed request for a team January 20, 2022 15:49
@thisisommore
Copy link
Author

thisisommore commented Jan 20, 2022

@thisisommore I was looking through again and noticed you asked me to do a preliminary review. I'm happy to do one for you, however, can you please rebase your changes onto main first and resolve the conflicts? There have been a number of changes to the config since work was started on this PR.

@wilkermichael sorry for being late, I have merged main into this PR, also I was not going into the test due to now sure where to start, I hope this is not urgent or feel free to assign this to someone else, I will be trying hands on it again this Sunday.

Also Happy New Year 🥳, hope you are doing well.

@lornasong lornasong requested review from wilkermichael and removed request for lornasong January 20, 2022 17:20
@wilkermichael
Copy link
Contributor

@thisisommore Happy New Year!

No rush on this, take your time. I probably won't get a chance to review until next week.

Copy link
Contributor

@wilkermichael wilkermichael left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @thisisommore I have reviewed some of your submission, but noticed that there are some build issues as well as some tests which I don't think will pass. Can you please resolve all build issues and confirm the unit tests are passing?

You can run the following to verify this behaviour:

  1. make dev will attempt to build CTS
  2. make test will run the unit tests

Thanks for all your hard work, an I will continue the review after these are resolved

@@ -181,7 +181,7 @@ func (cli *CLI) runBinary(configFiles, inspectTasks config.FlagAppendSliceValue,

// Print information on startup for debugging
logger.Info(version.GetHumanVersion())
logger.Debug("configuration", "config", conf.GoString())
logger.Debug("configuration", "config", conf.String())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cool thing about the Stringer interface is that the logger will actually try and call the interface without the implementer needing to explicitly call the String() method. In this way the logger can marshal to JSON or call String() when printing plaintext.

Can you remove the String() call from instances where the logger calls the String() method?

eg. in this case logger.Debug("configuration", "config", conf)

Password *string `mapstructure:"password"`
Enabled *bool `mapstructure:"enabled" json"enabled"`
Username *string `mapstructure:"username" json"username"`
Password *string `mapstructure:"password" json"password"`
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need a : between json and the field name
i.e. json:"enabled"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you also use json:"-" for the password string, this shouldn't be included when printing the json

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try to catch similar scenarios where you should use json:"-" but if you see it's printing the value using the sensitiveGoString() function in the String() method, then the json tag should be redacted as well.

"%s"+
"}",
c.CatalogServicesMonitorConfig.GoString(),
c.CatalogServicesMonitorConfig.String(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can remove .String() usage here as well, and all other usages like this

return fmt.Sprintf("&ConsulKVConditionConfig{"+
"%s, "+
"UseAsModuleInput:%v"+
"}",
c.ConsulKVMonitorConfig.GoString(),
BoolVal(c.UseAsModuleInput),
>>>>>>> upstream/main
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please fix this merge conflict


return fmt.Sprintf("&ScheduleConditionConfig{"+
return fmt.Sprintf("{"+
"Cron:%s, "+
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing json tag for Cron (json:"cron") on above ScheduleConditionConfig struct (line: 15)


return fmt.Sprintf("&ServicesConditionConfig{"+
"%s, "+
"UseAsModuleInput:%v"+
"}",
c.ServicesMonitorConfig.GoString(),
c.ServicesMonitorConfig.String(),
BoolVal(c.UseAsModuleInput),
)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing json tags on ServicesConditionConfig struct. Most likely these were added after you merged main

sensitiveGoString(c.Token),
c.Transport.GoString(),
c.Transport.String(),
)
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing json struct tags on ConsulConfig

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a special case here, notice how c.Token is "sensitive" in this case I think it would be appropriate to use the special json tag json:"-" so as to not include this field when printing out

@wilkermichael wilkermichael force-pushed the thisisommore/log-struct-key-values-377 branch from 3437cd4 to 00ea382 Compare January 28, 2022 21:17
@wilkermichael
Copy link
Contributor

@thisisommore I changed some settings on our end as I noticed that the pipeline wasn't running for your PR. The checks should now show the areas where the PR is failing.

@wilkermichael
Copy link
Contributor

wilkermichael commented Jan 28, 2022

You can ignore the Slack failure, that one is on our end. It requires a setting change, but won't affect your PR.

EDIT: This has been resolved now in the latest main, if you rebase on the latest main it should resolve the slack failure in the pipeline

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Log struct key-values instead of struct string blobs
3 participants