butane icon indicating copy to clipboard operation
butane copied to clipboard

chore: package dependencies debt

Open vic1707 opened this issue 1 month ago • 10 comments

PR implementing: https://github.com/coreos/butane/pull/631#issuecomment-3529809716 in order to fix the circular dependency of #631.

From @prestist

New Structure:

translate/              ← NEW: Top-level package
  interface.go          ← Define Translator interface
  registry.go           ← Registry implementation

base/v0_7_exp/
  schema.go             ← Data structures only
  translator.go         ← Implements translate.Translator interface

config/fcos/v1_7_exp/
  schema.go             ← Wraps base schemas
  translator.go         ← Implements translate.Translator interface
  init.go               ← Registers with translate.Registry

[...] So I would recommend that we do it in stages,

  1. define the interfaces.
  2. review that
  3. Implement the use of the interfaces

vic1707 avatar Nov 16 '25 20:11 vic1707

First attempt at 1. define the interfaces + 2. review that,

A translate module already existed and already exposed the various interfaces we want to move/modify, so for now I created them in a new code translator module. Once we find a suitable new interface and I implement it, I'll be removing dead code and renaming the translator module back to translate.

What do you think of the current proposal @prestist (sorry for the multiple pings today 🙇) ? I feel like the already existing interface was good enough so I basically copy-pasted it. Only the registry is new here 🤔.

vic1707 avatar Nov 16 '25 20:11 vic1707

@vic1707 No worries on the pings! Looking now.

prestist avatar Nov 17 '25 14:11 prestist

Hi @prestist, sorry it took so long, lots of stuff going on right now 😓 just pushed a v2, while it's probably better, I'm not convinced it's right, the Metadata getter on the Translator doesn't feel right.

I didn't touch much on the registry as I'm not sure I understand what you meant from set(register) a top level function taking in the registry?

vic1707 avatar Nov 25 '25 14:11 vic1707

@vic1707 No worries, and there is no rush on this. I will take a look today and see what I can do to help :)

Sorry I am a bit behind on this, been feeling under the weather today.

prestist avatar Nov 25 '25 15:11 prestist

@vic1707 I gave it a shot, there is likely something I am overlooking but this is my interpretation of what we need.

You can see the registry is basically a map, which we can register all of our implementations on.

The next step would be to implement this interface and see where the holes are, I would probably try it with base07_exp config and lets see.

prestist avatar Nov 26 '25 20:11 prestist

@prestist thanks for your commit, I think I better understand now. I'll try to implement it asap and see were this goes 🙃

Quick question, in the Translator interface, you did

	Validate(ctx context.Context, input []byte) (report.Report, error)

I wonder if this is the right interface, I would've gone with

	Validate(ctx context.Context, input interface{}) (report.Report, error)

Since the current methods used for validation work on the schema datastruct, not the bytes input 🤔 ie:

func (t Tree) Validate(c path.ContextPath) (r report.Report) {
	if t.Local == "" {
		r.AddOnError(c, common.ErrTreeNoLocal)
	}
	return
}

I feel like having

	// Parse yml into schema struct
	Parse(input []byte) interface{} // Is basically a yaml.UnMarshal wrapper?
	// From schema struct to Ignition struct
	Translate(input interface{}, options common.TranslateBytesOptions) (interface{}, report.Report, error)
	// Validates yml struct
	Validate(in interface{}) report.Report

would make more sense? IMO, the translator's job is to expose its functionalities, the registry will be calling them and has the responsibility to call Validate (assuming we don't force users to validate at the translator level)

vic1707 avatar Nov 28 '25 13:11 vic1707

@vic1707 Great question! Sorry for the slow reply – holidays had me away.

I'm pretty sure we want to keep the proposed signature, but I could be wrong.

My thoughts:

The Validate(ctx context.Context, input []byte) (report.Report, error) signature works at the interface level because:

  • Abstraction - we hide the interface{} from the caller
  • Consistency - matches patterns like TranslateBytes
  • Encapsulation - the whole workflow (parse → validate → translate) lives inside the translator implementation, not exposed through the interface

Why not expose interface{} in the signature?

In doing that, we'd would make the caller have more complex requirements - they would need to know the internal flow (parse → validate → translate), which breaks encapsulation and makes the API harder to use. So instead keep the complexity in the implementation.

The main goal is to keep the interface simple, maintain encapsulation, and make the registry easy to use.

prestist avatar Dec 02 '25 15:12 prestist

Hi, No worries, free time is also hard to find on my end (renovation work + job) 😓

Ok I think I get your point, but it raises one question

You expect for all Translators to have

func (t *FCOS0.7) Validate(ctx context.Context, input []byte) (report.Report, error) {
	yaml, err := yaml.Unmarshal(input)

	// Validation steps

	return report, nil
}

func (t *FCOS0.7) Translate(ctx context.Context, input []byte, opts Options) (Result, error) {
	report, err := t.Validate(ctx, input)
	// ...
}

Validate returns an error or a Report, not the interface{}. That means Translate uses Unmarshal a second time itself (and that for each butane files, even imported modules)? if not where should we use the opts?

For now I don't have a better interface to submit matching your requirements 🤔


But, if I can cycle back to my edited proposition,

	// Parse yml into schema struct, basically a yaml.Unmarshal wrapper?
	Parse(input []byte, /*opts?*/) (interface{}, error)
	// From inner schema struct to Ignition struct
	Translate(input interface{}, options common.TranslateBytesOptions) (interface{}, report.Report, error)
	// Validates yml inner struct
	Validate(in interface{}) (report.Report, error)

Wouldn't the TranslatorRegisty be the abstraction not exposing the inner interface{}? We would get a registry with

func TranslateBytes([]byte input, options common.TranslateBytesOptions) (Result, error) {
	t, err := // get the right translator
	// Parse into interface{}
	// Validate interface{}
	// Translate
	return res, nil
}

This maintains the Consistency, Abstraction (technically) but the encapsulation workflow moves to the registry. It would, IMO, be beneficial to potential downstream users of the lib who would get access to all the inner workings. ~~tests could make use of unvalidated schemas if needed (partial ones)~~ not relevant, but I feel like tests could be better/easier to write with this approach, the methods are more independent

vic1707 avatar Dec 08 '25 21:12 vic1707

But, if I can cycle back to my edited proposition,

	// Parse yml into schema struct, basically a yaml.Unmarshal wrapper?
	Parse(input []byte, /*opts?*/) (interface{}, error)
	// From inner schema struct to Ignition struct
	Translate(input interface{}, options common.TranslateBytesOptions) (interface{}, report.Report, error)
	// Validates yml inner struct
	Validate(in interface{}) (report.Report, error)

Wouldn't the TranslatorRegisty be the abstraction not exposing the inner interface{}? We would get a registry with

func TranslateBytes([]byte input, options common.TranslateBytesOptions) (Result, error) {
	t, err := // get the right translator
	// Parse into interface{}
	// Validate interface{}
	// Translate
	return res, nil
}

This maintains the Consistency, Abstraction (technically) but the encapsulation workflow moves to the registry. It would, IMO, be beneficial to potential downstream users of the lib who would get access to all the inner workings. ~tests could make use of unvalidated schemas if needed (partial ones)~ not relevant, but I feel like tests could be better/easier to write with this approach, the methods are more independent

Oooh, you are absolutely right, yes, the Registry should be the abstraction layer, not the Translator interface itself, I think that approach should work well. Sorry for the initial push back. I was a bit focused on the wrong parts. The double unmarshaling sounds terrible lol.

In summary, yeah, I think your edited approach would be correct and should get us there.

prestist avatar Dec 10 '25 15:12 prestist

happy I pushed back a little 😄 I should get some free time over the weekend to implement one translator, we'll see how this goes

vic1707 avatar Dec 12 '25 09:12 vic1707