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

offline-updates: Allow specification of local ostree repo #439

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

Conversation

detsch
Copy link
Member

@detsch detsch commented Oct 31, 2024

Allowing the specification of a local ostree repo during offline bundle creation is required when a custom ostree repo is in use (fiopush + fioctl targets add).

This PR also add a warning message if the ostree repo does not contain the expected hash.

@detsch
Copy link
Member Author

detsch commented Oct 31, 2024

@mike-sul please take a look. I can't add reviewers to the PR.

Copy link
Member

@vkhoroz vkhoroz left a comment

Choose a reason for hiding this comment

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

I defer the review to @mike-sul indeed.

Just recommending to use stdlib to a greater extent.

subcommands/targets/offline-update.go Outdated Show resolved Hide resolved
This option allows a local ostree repo to be specified as replacement
for the factory ostree repo when building the offline bundle.
This if required when a custom ostree repo was pushed with `fiopush` and
a target was was manually added referencing a hash from this custom
repo.

Signed-off-by: Andre Detsch <[email protected]>
When a target was manually added with `fioctl targets add`, it may
reference a ostree hash that is not included the factory's repo.
An offline bundle created in such condition will fail installation,
so printing a warning is appropriate.

Signed-off-by: Andre Detsch <[email protected]>
@detsch detsch force-pushed the detsch-add-ostree-repo-source branch from 47696c0 to 0ab88cd Compare November 4, 2024 23:38
@@ -87,6 +89,8 @@ func init() {
"Skip fetching Target Apps")
offlineUpdateCmd.Flags().BoolVarP(&ouAllowMultipleTargets, "allow-multiple-targets", "", false,
"Allow multiple targets to be stored in the same <dst> directory")
offlineUpdateCmd.Flags().StringVarP(&ouOstreeRepoSrc, "ostree-repo-source", "", "",
"Path to the local ostree repo to be used in the offline bundle")
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe "to be added to the offline bundle" or "to be copied to"?

}
defer dstFile.Close()

_, err = dstFile.ReadFrom(srcFile)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't have strong opinion, just io.Copy seems like would be more flexible.

Copy link
Member

Choose a reason for hiding this comment

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

I thought io.Copy could replace the entire function, but unfortunately it does not.

No idea what flexibility you mean... a doc says this:

If src implements [WriterTo](https://pkg.go.dev/io#WriterTo),
the copy is implemented by calling src.WriteTo(dst).
Otherwise, if dst implements [ReaderFrom](https://pkg.go.dev/io#ReaderFrom),
the copy is implemented by calling dst.ReadFrom(src).

So it seems to only add an extra function call.

Copy link
Contributor

Choose a reason for hiding this comment

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

By flexibility I meant exactly what the doc extract says - it agnostic to details of src and dst implementation. Their type change won't need to change io.Copy() call.
As I said, I don't have strong opinion, just looks more uniform to use io.Copy.

}
defer srcFile.Close()

dstFile, err := os.Create(dst)
Copy link
Contributor

Choose a reason for hiding this comment

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

I would improve the overall file copying process by:

  1. Checking if dst exists, and skip copying if it does.
  2. At first copy to some tmp file (e.g. dst.tmp ) and then "Sync" & rename to the real name (dst).

It will help to avoid to recopying files if the command is interrupted and then a new attempt is invoked. Also, it makes sure that files are properly persisted on storage (sync).

Copy link
Member

Choose a reason for hiding this comment

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

(2) probably makes sense... although I'm not a great fan of a trade-off (added complexity).

As for (1), I think it comes with inherent risk of copying wrong files.
Think of it this way: a user might supply a wrong source, interrupt, and then supply a correct source.
If we don't forcibly replace all files, we can end up with the wrong files.

Not sure how accurate are my statements. Maybe, ostree has a directory structure preventing the above.
So, I leave it up to you to weigh on this.

Copy link
Contributor

Choose a reason for hiding this comment

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

(2) is important if a user copies files to external thumb drive to guarantee data are synced.

Copy link
Contributor

Choose a reason for hiding this comment

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

a user might supply a wrong source, interrupt, and then supply a correct source.

I don't see how (1) would cause an issue here taking into account (2) and the fact that each file in ostree repo has unique name - its hash.

Copy link
Member

@vkhoroz vkhoroz Nov 5, 2024

Choose a reason for hiding this comment

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

each file in ostree repo has unique name - its hash

Told you, I can be missing something.

how (1) would cause an issue here

But, I must be backing my claims about safety still.
You see, fsync only makes sure to sync the write to the hardware level, but that data may still be stored in the hardware (firmware) level cache first, and only later written to the persistent storage.
So, if we are talking about e.g. a thumb drive, than I'd say to do one of the following:

  1. Always rewrite - easy way.
  2. If file exists - check the file size/hash, and rewrite if it differs.

Trust me, I remember those times when I had to trash 1 in 100 CDs, because all those "flush to disk" things are never as reliable as assumed.

Copy link
Contributor

Choose a reason for hiding this comment

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

check&write vs rewrite and "write to tmp" and fsync are two different things.
If we write to tmp then a file does not exist until it's fsynced and renamed to the file correct name. If we assume that "fsync and rename" are 99.9999% guarantee that data are persisted then we don't need to check file hashes. We use this techniques in fioconfig and aklite and composectl and we haven't seen any issues with it so far.

sha256String := base64.StdEncoding.EncodeToString(ti.sha256)
expectedCommit := path.Join(dstDir, "ostree_repo", "objects", sha256String[0:2], sha256String[2:]+".commit")
if _, err := os.Stat(expectedCommit); errors.Is(err, os.ErrNotExist) {
fmt.Printf("WARNING: ostree repo does not contain target's hash %s, installation will fail. If the target references a custom ostree repo, re-run specifying --ostree-repo-source.\n", sha256String)
Copy link
Contributor

Choose a reason for hiding this comment

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

IMO, this is the critical error, it doesn't make sense to ship bundle to devices if the commit hash is missing, so I would just exit/die at this spot.

@@ -180,6 +182,15 @@ Notice that multiple targets in the same directory is only supported in LmP >= v
fmt.Printf("Downloading an ostree repo from the Target's OE build %d...\n", ti.ostreeVersion)
subcommands.DieNotNil(downloadOstree(factory, ti.ostreeVersion, ti.hardwareID, dstDir), "Failed to download Target's ostree repo:")
}

if ti.sha256 != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

The hash ti.sha256 is mandatory, it is nil I would just die.

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.

3 participants