Skip to content

utils and utils.create_from_yaml improvement suggestions #722

Description

@oz123

create_from_yaml should accept a string like object, like yaml.load and safe_safe do.
Then, it's the user's task to read the file.
To help users we can add a function create_from_file which accept a file path. create_from_file will than call create_from_yaml.

The new signatures should be:

def create_from_yaml(k8s_client, yaml_str, verbose=False, **kwargs):
      """
      load yaml string and apply using `create_from_map`
      """
      stream = yaml.safe_load_all(yaml_str)
      for obj in stream:
            create_from_map(k8s_client, obj, verbos)

      ...

def create_from_file(k8s_client, path_str, verbose=False, **kwargs):
      """
      read a file and continue with create_from_yaml
      """
      ...

Additionally, we should add:

 def create_from_json(k8s_client, json_str, verbose=False, **kwargs):
       """
       read a json like string  and apply by using create_from_map
       """
       ...

 def create_from_map(k8s_client, dict_like, verbose=False, **kwargs):
       """
       does all the logic previously in create_from_file.
       """

This approach brings the python client a little bit closer to

kubectl create -f <filename>

which accepts either files and streams from stdin, file, formatted as json and yaml).

I will happily prepare patch for this.

Activity

  1. micw523 commented on Jan 16, 2019

    @micw523
    Contributor

    The YAML loaders should parse (most) JSON correctly especially those created by kubectl so I don’t think the JSON part is needed.

    I don’t think you use the -f flag if you’re parsing from stdin though.

  2. oz123 commented on Jan 16, 2019

    @oz123
    ContributorAuthor

    It's not usual to do this, but I have seen people do this in scripts, for example:

    dnscheck_pod='{"apiVersion":"v1","kind":"Pod","metadata":{"labels":{"k8s-app":"dnscheck"},"name":"dnscheck","namespace":"default"},"spec":{"containers":[{"image":"tutum/dnsutils","name":"dnscheck","command":["sleep"],"args":["86400"]}],"securityContext":{}}}'; 
    echo ${dnscheck_pod} | kubectl --kubeconfig=${KUBECONFIG} apply -f -; 
    

    In any case, would you accept a PR with my suggestions?

  3. micw523 commented on Jan 16, 2019

    @micw523
    Contributor

    I take 50% of what I said back - create -f - takes input from stdin. It’s not super unusual either - this kind of usage appears on the official documentation. It would require an extra parameter if we implement in Python: file name as - and a string input.

    I’m conflicted on how useful this would be. It would be simple enough to implement though on top of the create_from_yaml we have now. Please do note that the function is not yet complete in the master branch. Probably we can wait a little bit to see how many users want this feature.

  4. oz123 commented on Jan 16, 2019

    @oz123
    ContributorAuthor

    Currently, the function create_from_yaml requires a file. Would you consider accepting the PR
    with just my first two suggestions ?

    If so, my PR will include only these changes:

    • change create_from_yaml to accept yaml string
    • add a function which explicitly reads from file: create_from_file
  5. micw523 commented on Jan 16, 2019

    @micw523
    Contributor

    I'd rather not do it since your suggestion will break existing API while not necessary. If the addition of feature is deemed necessary, I'd rather make the change inside create_from_yaml and make create_from_file an alias.

  6. oz123 commented on Jan 16, 2019

    @oz123
    ContributorAuthor

    @micw523
    Actually, I like your suggestion! I can do this as you suggested.

    create_from_yaml can check if the string passed to it is a file system path, if it is we'll read it as as yaml. If it's not we can can directly load the yaml.

  7. juliantaylor commented on Mar 7, 2019

    @juliantaylor
    Contributor

    The function should also support file handles instead of path strings, so if you pass it a StringIO object, stdin or an already open file with valid yaml it will also work.

    something like the suggested create_from_map would indeed be very useful. It is currently quite inconvenient if you want to create multiple objects with minor changes (like different namespace)

  8. eagle9527 commented on Mar 7, 2019

    @eagle9527

    I think the Python interface, using JSON is very common, I create applications are the transfer of JSON format data for application creation, easy to use, integration into their own platform is also very convenient.

  9. dee-me-tree-or-love commented on Mar 18, 2019

    @dee-me-tree-or-love

    That would be a very convenient feature, has anyone started working on it?

    Also wouldn't it be useful to expand the API with providing custom resovlers and constructors for the yaml loader? It would be possible then to use the environment variables when loading the file. If there is any better way to do it already, would appreciate a suggestion, otherwise I could pick that up :)

    https://pyyaml.org/wiki/PyYAMLDocumentation#constructors-representers-resolvers
    @oz123 @micw523 what do you think?

  10. oz123 commented on Mar 18, 2019

    @oz123
    ContributorAuthor

    @dee-me-tree-or-love I have a branch where this is already partially implemented. I need to clean it and make a PR. Will do so this week.

  11. added a commit that references this issue on Mar 18, 2019
  12. oz123 commented on Mar 18, 2019

    @oz123
    ContributorAuthor

    @dee-me-tree-or-love I pushed my branch. I agree that custom resovlers and constructor could be useful, but in order to enhance the chance that this PR is accepted I think the scope should be minimum.
    A follow up request can enhance this.

  13. micw523 commented on Mar 19, 2019

    @micw523
    Contributor

    @dee-me-tree-or-love I'm not sure if we need a custom class for loading YAML since

    1. the whole YAML body should be passed into the function calls for performing an action specified in the YAML file
    2. we pretty much know what kind of things we need to extract from the YAML file anyway...?
      Let me know if I misunderstood.
  14. micw523 commented on Mar 19, 2019

    @micw523
    Contributor

    /assign
    /assign @oz123

  15. 3 remaining items

  16. micw523 commented on Mar 20, 2019

    @micw523
    Contributor

    Can you reopen this, @oz123? We could still get something done here.

  17. oz123 commented on Mar 20, 2019

    @oz123
    ContributorAuthor

    @micw523 I appreciate you not giving up on this. I will need more guidance as for what can be still added.

  18. reopened this on Mar 20, 2019
  19. micw523 commented on Mar 20, 2019

    @micw523
    Contributor

    As a start, the e2e_test folder now contains a few extra yaml files that weren’t there before. There are a few files that are List kind or contain multiple resources in them. Those cannot be handled by the create_from_yaml_single_itemfunction, since that function only supports an explicit kind.

    What we can do is approximately the same thing you did it before and still change the create_from_yaml function to handle a dict input or a string input. What do you think?

  20. micw523 commented on Mar 20, 2019

    @micw523
    Contributor

    Another reason the create_from_yaml_single_item get segmented out is that if we should have a dynamic client in the future this function will likely become obsolete and be replaced by an implementation based on the dynamic client. Be assured, it’s not rendering anything that you did obsolete.

  21. dee-me-tree-or-love commented on Mar 25, 2019

    @dee-me-tree-or-love

    hello @oz123 @micw523, sorry I have been not responsive, great to see the progress, thanks!
    @micw523 what I meant with the custom resolvers is to provide the functionality to easier support variable substitution.

    # nginx-deployment.yaml (silly example file)
    apiVersion: v1
    kind: Service
    metadata:
      name: ${SERVICE_NAME}
    spec:
      ports:
      - port: ${SERVICE_PORT}
        targetPort: ${TARGET_PORT}
        name: web-app
      - port: 80
        targetPort: 80
        name: nginx
      type: ${SERVICE_PORT_TYPE}

    and then

    # Define the resolvers and the constructors
    #   see https://pyyaml.org/wiki/PyYAMLDocumentation#constructors-representers-resolvers
    path_matcher = re.compile(r'\$\{([^}^{]+)\}')
    def path_constructor(loader, node):
      ''' Extract the matched value, expand env variable, and replace the match '''
      value = node.value
      match = path_matcher.match(value)
      env_var = match.group()[2:-1]
      return os.environ.get(env_var) + value[match.end():]
    
    def main():
      # Configure the utils to use the resolver and constructor
      utils.add_implicit_yaml_resolver('!path', path_matcher)
      utils.add_yaml_constructor('!path', path_constructor)
    
      # Now all the environment variables will be substituted on the flight
      utils.create_from_yaml(k8s_client, "nginx-deployment.yaml")
      ...

    Thought of this from this discussion https://stackoverflow.com/questions/26712003/pyyaml-parsing-of-the-environment-variable-in-the-yaml-configuration-file

    Maybe there is a better way to do this already, which I am not aware of already, would very much appreciate a suggestion if so, sorry for a long message and if I misunderstood something

  22. juliantaylor commented on Mar 25, 2019

    @juliantaylor
    Contributor

    @dee-me-tree-or-love adding such functionality to the kubernetes python client is a bad idea, you are mixing features of a specific yaml deserializer implementation with a generic api client and these two things should have no connection besides that the output of one (which is an generic python object) can be the input of the other.
    Besides that you are mixing two orthogonal APIs what about other serialization formats like json, msgbuf, xml etc.? the pyyaml API won't work on these formats.

    But your feature will be more easily possible with the proposed improvements as as it decouples the data format from the input to the function so you can use these features more freely, e.g. your code could then look like this:

    yaml.add_yaml_constructor('!path', path_constructor)
    # ....
    with open(data) as f:
       utils.create_from_mapping(k8s_client, yaml.load(f))
    

    (you can already do that right now by writing the parsed yaml to a NamedTempfile, but that is very ugly which is why this issue exists)

  23. dee-me-tree-or-love commented on Mar 25, 2019

    @dee-me-tree-or-love

    @juliantaylor thanks a lot for the suggestion, indeed, I definitely didn't take the big picture into the consideration and made quite a mistake in mixing the boundaries... thanks a lot for pointing out this fact, it is a very good learning!

  24. added a commit that references this issue on Mar 26, 2019
  25. oz123 commented on Mar 26, 2019

    @oz123
    ContributorAuthor

    @micw523 please see my PR #795 . This is my suggestion on how to allow creating of k8s objects
    from python dicts, and also allow passing of strings without breaking the existing API.

    A later version could deprecate usage of create_from_yaml with a file, and require direct usage
    via create_from_file. But I leave this now out of the PR.

    Also, I still have not exposed create_from_map by importing it in kuberentes/utils/__init__.py.
    As far as I understand from the discussion here, there is a desire by the participants here that this would be done, but I would like to hear a second opinion.

  26. added a commit that references this issue on Mar 26, 2019
  27. dee-me-tree-or-love commented on Jun 20, 2019

    @dee-me-tree-or-love

    Friendly bump, is this still on the review? :)

  28. oz123 commented on Jun 20, 2019

    @oz123
    ContributorAuthor

    @dee-me-tree-or-love @micw523 hasn't had any public activity since April, and I would appreciate it if we could merge this PR. It's been rotting here, and since I can't merge this one, I put many other contributions on hold.

    Also, there is another PR with a similar intent #770, which has been rotting. This functionality is really wanted (and needed), so please merge this. It would be a good community service.

  29. oz123 commented on Jul 24, 2019

    @oz123
    ContributorAuthor

    This issue is finally fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions