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

Clone the content of a node to another #665

Closed
mojito317 opened this issue May 10, 2022 · 7 comments
Closed

Clone the content of a node to another #665

mojito317 opened this issue May 10, 2022 · 7 comments
Assignees
Labels
carvel triage This issue has not yet been triaged for relevance helping with an issue Debugging happening to identify the problem

Comments

@mojito317
Copy link

mojito317 commented May 10, 2022

I would like to clone (without duplication) the right side of an A node to a B node keeping A node as is. Can I somehow reach this with overlays in ytt?

Example input:

A:
  e: f
  c: d

Expected output:

A:
  e: f
  c: d
B:
  e: f
  c: d
@mojito317 mojito317 added the carvel triage This issue has not yet been triaged for relevance label May 10, 2022
@pivotaljohn
Copy link
Contributor

Hey @mojito317, good question.

Yes, you can.

If you mean wholesale duplicate a single key, you can use the @overlay/replace operation to capture and duplicate the contents:

#@overlay/match by=lambda _,left,__: "A" in left
#@overlay/replace via=lambda orig, _: {"A": orig["A"], "B": orig["A"]}
---

Note:

  • I'm using a custom matcher to ensure that I only match on documents that contain the A: key at the top level.
  • Using a convention of when a parameter is not used replace with an underscore (_)... and when there are two parameters not being used, replace with a double-underscore (__).
  • we're using @overlay/replace via= and building up a Starlark dictionary (type=dict) that contains two entries: one for the key A: and one for the key B: ... and ytt will automatically encode that into YAML. 👍🏻

However, if there are other parts of the document, e.g.

A:
  e: f
  c: d
G:
  h: i

that approach will remove them (here, G: is removed).

...

If you need a more general approach, this works:

#@ load("@ytt:yaml", "yaml")

#@ def dup(orig, src, dest):
#@   result = yaml.decode(yaml.encode(orig))
#@   result[dest] = result[src]
#@   return result
#@ end

#@overlay/match by=lambda _,left,__: "A" in left
#@overlay/replace via=lambda orig, _: dup(orig, "A", "B")
---

Note:

  • input YAML fragments are not directly editable. To produce an editable value, we convert the input into a string (i.e. yaml.encode()) and then parsing that string (i.e. yaml.decode()) into Starlark values (i.e. a dictionary of dictionaries). (see @ytt:yaml).
  • from there, we can make whatever imperative edits we want and then return that result.

...

All of this makes me quite curious: what in your world is wanting to create a duplicate like this? 🤔
I don't mean to question it, but better understand what you're dealing with.

@pivotaljohn pivotaljohn added helping with an issue Debugging happening to identify the problem and removed carvel triage This issue has not yet been triaged for relevance labels May 10, 2022
@mojito317
Copy link
Author

@pivotaljohn thank you for your help, I would not figure this out by myself. I kept trying with the replace, but I was far from this solution.

All of this makes me quite curious: what in your world is wanting to create a duplicate like this? 🤔
I don't mean to question it, but better understand what you're dealing with.

It's a tricky one. We are using ytt for overlaying an OpenAPI descriptor, where we have one schema with a tiny difference from the original, but we want to keep both of them, so we have to duplicate it before we modify our custom one.

I provided helpful modification for the future if someone wants to duplicate one nested part of a tree. In this example I would like to duplicate the right of A.B.C to A.B.X while keeping A.B.C too:

input:

A:
  B:
    C:
      d: e
  F:
    G:
       h: i

overlay:

#@ load("@ytt:yaml", "yaml")

#@ def dup(orig, src, dest):
#@   result = yaml.decode(yaml.encode(orig))
#@   result["A"]["B"][dest] = result["A"]["B"][src]
#@   return result
#@ end

#@ load("@ytt:overlay", "overlay")
#@overlay/match by=lambda indexOrKey, left, right: "C" in left["A"]["B"]
#@overlay/replace via=lambda left, right: dup(left, "C", "X")
---

Output:

A:
  B:
    C:
      d: e
    X:
      d: e
  F:
    G:
      h: i

@pivotaljohn pivotaljohn self-assigned this May 11, 2022
@pivotaljohn
Copy link
Contributor

I'm delighted you got there. Happy to help.

Dropping your questions in a GitHub issue like you just did is good and spot on.

Note that you can also get support on our Kubernetes Slack channel: #carvel. If you're not already in the Kubernetes Slack workspace, you can get an invite here: http://slack.k8s.io/

@pivotaljohn
Copy link
Contributor

Closing this issue to note that we've got a solution. If you'd like any additional support feel free to ask!

@lorenzobenvenuti
Copy link

Hi,

sorry to resurrect an old thread, but I have a related question: is there any way to do something like this to clone an entire document? Basically, changing this

---
A:
  B: foo

into

---
A:
  B: foo
---
A:
  B: bar

I tried to play with the example above but if I return a docset from the dup function I get an error.

Thanks

@GUI
Copy link

GUI commented Jul 1, 2023

@lorenzobenvenuti: I was looking for similar functionality (cloning a document), and I think it's possible using overlay.insert with a function (support added in #742).

So, for example, if you just need to clone a document (without making any changes):

Input:

#@ load("@ytt:overlay", "overlay")

---
A:
  B: foo

#@overlay/match by=overlay.all
#@overlay/insert after=True, via=lambda left, right: left
---

Output:

A:
  B: foo
---
A:
  B: foo

But for a fuller example, you can also combine this with overlay.apply to create a new cloned document with changes merged on top:

Input:

#@ load("@ytt:overlay", "overlay")

---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
  namespace: example-namespace

#@overlay/match by=overlay.subset({"metadata":{"name":"example-ingress"}})
#@overlay/insert after=True, via=lambda left, right: overlay.apply(left, right)
---
metadata:
  name: cloned-ingress
#@overlay/match missing_ok=True
spec:
  ingressClassName: alb

Output:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
  namespace: example-namespace
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cloned-ingress
  namespace: example-namespace
spec:
  ingressClassName: alb

@github-actions github-actions bot added the carvel triage This issue has not yet been triaged for relevance label Jul 1, 2023
@lorenzobenvenuti
Copy link

TYVM! That's exactly what I was looking for!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
carvel triage This issue has not yet been triaged for relevance helping with an issue Debugging happening to identify the problem
Projects
None yet
Development

No branches or pull requests

4 participants