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

Support passing in -env /path/to/env.json at installation time. #704

Open
wants to merge 11 commits into
base: v0.10.x
Choose a base branch
from

Conversation

isc-shuliu
Copy link
Collaborator

@isc-shuliu isc-shuliu commented Jan 14, 2025

Implement #632

  • Allow users to pass in one or more files at install time using syntax like install <pacakge> -env /path/to/json1;/path/to/json2
  • Added a new singleton class %IPM.General.InstallTimeConfig
  • Package developers can access this config using
     ##class(%IPM.General.InstallTimeConfig).GetArg("<their-package-name>", "key", "nested-key", "nested-nested-key")
  • The env.json should have package name as top-level keys, so that when installing dependencies, the env.json is also available.
    {
      "my-package": { "key1": "value1" },
      "one-dependency": { "key2": "value2" },
      "another-dependency": { "key3": "value3" }
    }
  • When multiple env.json files are provided, later ones override the earlier ones during merging
  • Special rules apply when merging json objects:
    • undefined can be merged with any type
    • Other mixed types (array vs object, array vs number, object vs string, etc) cannot be merged
    • When 2 arrays are merged, treat them as special objects with numeric keys, meaning we won't extend array1 with array2

@isc-shuliu isc-shuliu marked this pull request as ready for review January 14, 2025 15:08
@isc-kiyer
Copy link
Collaborator

@isc-shuliu general q: thoughts on having the helper method for consumers return the full json for their package? Gives them more control then instead of extracting individual keys

@isc-shuliu
Copy link
Collaborator Author

isc-shuliu commented Jan 14, 2025

@isc-kiyer
So ##class(%IPM.General.InstallTimeConfig).GetArg("<package-name>") will give them the full JSON for their package.

Do you think we should omit the <package-name> part, and maintain a context of the current package's name?

Copy link
Collaborator

@isc-kiyer isc-kiyer left a comment

Choose a reason for hiding this comment

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

@isc-shuliu looks mostly great! Few small comments/questions

src/cls/IPM/General/InstallTimeConfig.cls Outdated Show resolved Hide resolved
src/cls/IPM/General/InstallTimeConfig.cls Outdated Show resolved Hide resolved
src/cls/IPM/General/InstallTimeConfig.cls Outdated Show resolved Hide resolved
src/cls/IPM/General/InstallTimeConfig.cls Outdated Show resolved Hide resolved
While iter.%GetNext(.key, .value, .type) {
If (type = "object") || (type = "array") {
If dest.%GetTypeOf(key) = "unassigned" {
Do dest.%Set(key, value.%New())
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why does value need a %New() here?

Copy link
Collaborator Author

@isc-shuliu isc-shuliu Jan 15, 2025

Choose a reason for hiding this comment

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

So my idea was that, whenever dest[key] is empty:

  • if src[key] is an array, we assign an empty JSON array [ ] and then keep recursing
  • if src[key] is an object, we assign an empty JSON object { } and then keep recursing

During the recursive calls, the newly created empty array [ ] or empty object { } will be populated.

Throughout this recursive function, I only call dest.%Set() with primitive types, newly created array [ ], or newly created object { }. This ensures dest[key] doesn't reference the same object as src[key]. In other words, I create a deep copy of src[key] and assign to dest[key].

If we use Do dest.%Set(key, value) where value is src[key] and modify src[key] later, dest will also change. This is an unexpected behavior.

Quit ..Config.%ToJSON()
}

Method SetJson(json As %String)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Would we ever be setting from anything other than a file?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Not at the moment. But just in case in the future we need the ability to inject config from another source (say network stream), I added this method. Shall I remove it?

Set paths = $ListFromString(paths, ";")
Set ptr = 0
While $ListNext(paths, ptr, file) {
$$$ThrowOnError(config.Load(file))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should we also remove leading/trailing white space here before loading the file?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Whitespace is a valid character in file names on both Unix and Windows. But I think it's uncommon to have a trailing whitespace and totally illegal to have a leading one in an absolute path.
@isc-tleavitt Ideas on whether to strip the whitespace?

@@ -272,9 +272,13 @@ load https://github.com/user/repository.git -branch feature-1
<example description="Loads the module described in C:\module\root\path\module.xml but set pip timeout to 30 seconds">
load -extra-pip-flags "--timeout 30" C:\module\root\path\
</example>
<example description="Loads the module described in C:\module\root\path\module.xml using the C:\path\to\env.json as the install time configuration">
load -env C:\path\to\env.json C:\module\root\path
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should have semi-colon between the paths

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is actually an env.json file + a package folder. I'll add a couple examples to emphasize the "semicolon-separated" syntax.

// Methods with [CodeMode = objectgenerator] can then use this to access configuration set in env.json
Set itconfig = ##class(%IPM.General.InstallTimeConfig).%Get()
If 'itconfig.IsEmpty() {
Set qSpec = qSpec _ "/multicompile=0"
Copy link
Collaborator

Choose a reason for hiding this comment

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

How does multicompile affect regular use case outside of migration code?



<!-- Modifiers -->
<modifier name="env" aliases="e" dataAlias="EnvFiles" value="true" description="Semicolon separated paths to the environment files in json format. See wiki for details." />
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could env be added as a possible modifier for module-action too? A use case could be using it in a custom phase for invokes

@isc-kiyer
Copy link
Collaborator

@isc-kiyer So ##class(%IPM.General.InstallTimeConfig).GetArg("<package-name>") will give them the full JSON for their package.

Do you think we should omit the <package-name> part, and maintain a context of the current package's name?

Correct. I think we should keep the package-name part since one module may want to access package-name for another dependency perhaps? I could go either way on that though. I think it may be more complicated to keep context than is worth the effort though, especially since its a singleton and modules could be getting installed in parallel.

@isc-eneil
Copy link
Collaborator

@isc-kiyer So ##class(%IPM.General.InstallTimeConfig).GetArg("<package-name>") will give them the full JSON for their package.
Do you think we should omit the <package-name> part, and maintain a context of the current package's name?

Correct. I think we should keep the package-name part since one module may want to access package-name for another dependency perhaps? I could go either way on that though. I think it may be more complicated to keep context than is worth the effort though, especially since its a singleton and modules could be getting installed in parallel.

I can see a use case for Sales when we'd like to install the same package in different namespaces with different configuration. It might be necessary to impose a standard for how to do this. Perhaps within the json object that is the value of the key there could be keys corresponding to the namespace in which configuration is applied?

src/cls/IPM/Main.cls Show resolved Hide resolved
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.

4 participants