Compare commits

...

492 Commits
0.3 ... 0.8.7

Author SHA1 Message Date
Ettore Di Giacinto
4028c62367 Tag 0.8.7 2020-10-11 15:17:16 +02:00
Daniele Rondina
51f32c0614 Update vendor/ 2020-10-10 19:59:40 +02:00
Ettore Di Giacinto
3261b2af98 Drop also package file list from db entry 2020-10-10 18:53:43 +02:00
Ettore Di Giacinto
b88a81c7ed Add database command
- Allows to override the system db and create/remove entries as desired.
  The input format is the same metadata as the one generated by the
  artifacts. It contains the Package and the file list that we need.
- Add integration test

Closes #47
2020-10-10 18:26:40 +02:00
Daniele Rondina
d67cf2fa33 Revert "Do image export only if we have to generate the package"
This reverts commit 0857e53b03.
2020-10-07 10:14:06 +02:00
Ettore Di Giacinto
0857e53b03 Do image export only if we have to generate the package 2020-10-06 19:01:25 +02:00
Ettore Di Giacinto
1c1bdca343 Add only-target-package option to luet build 2020-10-06 17:57:57 +02:00
Ettore Di Giacinto
2cb0f3ab5d Tag 0.8.6 2020-10-04 19:58:13 +02:00
Ettore Di Giacinto
74246780d4 Support templated packages 2020-10-04 19:33:15 +02:00
Daniele Rondina
097ea37c97 compiler/artefact: remove debug println 2020-10-02 22:37:38 +02:00
Daniele Rondina
8e23bf139a contrib: Add example of config protect confile 2020-10-02 22:28:08 +02:00
Daniele Rondina
3ba70ae9bd contrib/config/luet.yaml: Add config_protect_skip option 2020-10-02 22:27:38 +02:00
Daniele Rondina
c64660b8d1 Permit to ignore config protect rules
* Added command line option --skip-config-protect

* Added config option config_protect_skip
2020-10-02 22:25:21 +02:00
Daniele Rondina
c8c53644f3 Avoid segfault if tree path doesn't exist 2020-09-17 08:05:05 +02:00
Daniele Rondina
9b381e5d19 Update github.com/Sabayon/pkgs-checker vendor 2020-09-12 16:07:04 +02:00
Ettore Di Giacinto
6f623ae016 Tag 0.8.5 2020-09-10 17:40:33 +02:00
Daniele Rondina
bd80f9acd2 cmd/tree/bump: Add --pkg-version/-p to set specific version 2020-08-30 08:54:38 +02:00
Daniele Rondina
045d25bb28 pkg/package: Add method SetVersion to DefaultPackage 2020-08-30 08:53:37 +02:00
Daniele Rondina
908b6d2bd4 cmd/tree/validate: Fix race and drop errs chan 2020-08-23 12:27:51 +02:00
Ettore Di Giacinto
a3ada624a7 Merge pull request #132 from mudler/validate-buildtime-deps
cmd/tree/validate: Integrate validation of buildtime deps
2020-08-22 12:10:34 +02:00
Daniele Rondina
09c7609a7f cmd/tree/validate: Add error summary 2020-08-20 11:36:56 +02:00
Daniele Rondina
a1acab0e52 cmd/tree/validate: Integrate validation of buildtime deps
By default both runtime and buildtime deps are checked.
With the option --only-buildtime is possible analyze only
buildtime deps or instead with the option --only-runtime
only the runtime deps.

Signed-off-by: Daniele Rondina <geaaru@sabayonlinux.org>
2020-08-20 11:09:39 +02:00
Daniele Rondina
93187182e5 pkg/compiler: Fix typo on error message 2020-08-19 19:24:46 +02:00
Daniele Rondina
5e7cd183be contrib/config/luet.yaml: Update config example 2020-08-08 17:55:26 +02:00
Ettore Di Giacinto
9c0f0e3457 ci: release with GH Actions 2020-08-08 11:54:11 +02:00
Ettore Di Giacinto
1120b1ee59 ci: fix typo 2020-08-07 23:36:33 +02:00
Ettore Di Giacinto
4010033e0c ci: fixup workflows 2020-08-07 19:30:30 +02:00
Ettore Di Giacinto
a076613f66 Fixup import path 2020-08-07 19:30:08 +02:00
Ettore Di Giacinto
c184b4b3bc ci: Pass env by in GH actions 2020-08-06 18:52:42 +02:00
Ettore Di Giacinto
40d1f1785b ci: Get deps before running unit tests 2020-08-06 18:22:13 +02:00
Ettore Di Giacinto
11944f4b8c Disable tty on docker integration test 2020-08-06 18:11:50 +02:00
Ettore Di Giacinto
6f41f8bd8d Add GH action workflows 2020-08-06 18:03:35 +02:00
Ettore Di Giacinto
95b125cb91 Pull images before executing diff tests 2020-08-06 18:03:00 +02:00
Ettore Di Giacinto
f676b50735 Tag 0.8.4 2020-08-05 19:37:32 +02:00
Ettore Di Giacinto
0c0401847e ci: pass PATH also on deploy steps 2020-08-05 19:35:41 +02:00
Ettore Di Giacinto
02a506a5c5 ci: pass PATH by 2020-08-05 19:24:17 +02:00
Ettore Di Giacinto
6f0b657e69 ci: keep envs 2020-08-05 19:21:20 +02:00
Ettore Di Giacinto
51378bdfb6 Run tests as root to verify caps 2020-08-05 19:19:33 +02:00
Ettore Di Giacinto
66513955c7 Compute image diffs internally
Is it more faster in this way as we already have all the needed folders
to the comparison extracted. In this way we don't repeat I/O operation
twice by calling container-diff.

Do not depend on container-diff anymore
2020-08-05 19:09:45 +02:00
Ettore Di Giacinto
694d8656d9 Add xattrs tests 2020-08-05 18:58:50 +02:00
Ettore Di Giacinto
c339e0fed2 Add symlink test 2020-08-05 18:57:27 +02:00
Ettore Di Giacinto
e30bb056d5 Drop IsFlagged from tests 2020-08-02 12:22:43 +02:00
Ettore Di Giacinto
052a551c0c Add "hidden" field to packages
Also drop residual of IsSet which isn't actually used

Related to #26
2020-08-02 11:31:23 +02:00
Ettore Di Giacinto
ffa6fc3829 Tag 0.8.3 2020-07-17 22:42:49 +02:00
Ettore Di Giacinto
07a1058ac1 Add cli option to skip packages if only metadata is present (without checking the image) 2020-07-17 22:42:03 +02:00
Ettore Di Giacinto
3af9109b99 Tag 0.8.2 2020-07-12 15:57:46 +02:00
Ettore Di Giacinto
6e3650d3af Add upgrade_old_repo_revision fixture 2020-07-12 15:38:02 +02:00
Ettore Di Giacinto
5dcf77c987 Add package buildtimestamp and luet upgrade --sync
Annotate the package build time when compiling, and use that from the
client to force upgrade of packages that changed the artifact, but
didn't changed any version.

The client can trigger this behavior with `luet upgrade --sync`
2020-07-12 15:29:38 +02:00
Daniele Rondina
ee0e70ed3d tree/pkglist: Now --deps orders for build/installation order 2020-07-05 10:44:47 +02:00
Daniele Rondina
364b5648b4 repository loader now support .yaml extension 2020-07-04 20:07:32 +02:00
Daniele Rondina
e28a4753f8 tree/validate: Use --deps instead of --rdeps (we support also build deps) 2020-06-27 19:42:03 +02:00
Daniele Rondina
d1d7f5aa74 tree/pkglist: Add --rdeps option for runtime deps 2020-06-27 19:27:45 +02:00
Daniele Rondina
e2260b6956 Add --no-spinner option 2020-06-27 16:45:49 +02:00
Ettore Di Giacinto
764a09ce0c Tag 0.8.1 2020-06-27 13:02:00 +02:00
Ettore Di Giacinto
910f1ad3fe Merge branch 'master' into develop 2020-06-27 13:01:14 +02:00
Ettore Di Giacinto
16e9d7b20c Use packageImage as builder image fingerprint
This allows to have an unique identifier for the builder image id against
different depgraphs combinations. The package fingerprint is not enough,
as an atom could have a difference deptree depending on the requires
constraints.

TODO: Don't use the full image name, but only the hash as a salt
(currently the salt contains ALSO a reference of the image-repository,
as such it doesn't allow to port a tree in a different docker registry)
2020-06-23 18:59:18 +02:00
Ettore Di Giacinto
6088664887 Drop gox-build make target 2020-06-13 18:40:55 +02:00
Ettore Di Giacinto
ee3b59348e Tag 0.8.0 2020-06-12 19:41:37 +02:00
Ettore Di Giacinto
bb41a0c074 Add upx -1 option 2020-06-12 18:47:18 +02:00
Ettore Di Giacinto
6b8f412138 Update vendor 2020-06-12 17:58:13 +02:00
Ettore Di Giacinto
6d68ed073d Show and detect extensions with cobra-extensions 2020-06-12 17:54:57 +02:00
Ettore Di Giacinto
7b51e83902 Merge pull request #119 from mudler/config-protect
Integrate feature config-protect like Gentoo/Funtoo do
2020-06-07 11:33:55 +02:00
Daniele Rondina
3a365c709b Integrate config-protect from package spec 2020-06-06 13:12:54 +02:00
Daniele Rondina
aaa73dc2ac Add integration test for config-protect 2020-06-06 12:35:16 +02:00
Daniele Rondina
0917c2703e helpers/archive: Refactor and add support for config protect when sameOwner is false 2020-06-06 12:34:44 +02:00
Ettore Di Giacinto
264e1e9652 Print inspect output as string 2020-06-06 11:32:47 +02:00
Ettore Di Giacinto
03cc5fcb76 Use cobra for build --full 2020-06-06 11:20:48 +02:00
Ettore Di Giacinto
8aafc7600c Print selected packages 2020-06-06 11:18:54 +02:00
Daniele Rondina
c87db16d31 tarModifierWrapperFunc: Now use strings.HasPrefix for match path 2020-06-06 10:38:11 +02:00
Daniele Rondina
cd903351b3 cmd/config: Add print of config protect data 2020-06-06 10:36:42 +02:00
Daniele Rondina
837eeb04ec config/config_protect: Fix load of configuration files 2020-06-06 10:35:38 +02:00
Ettore Di Giacinto
90a25406a0 Add --full option to build
Don't compile dependencies when computing all the compilation specs from
a tree if are among the target deps

Fixes #41
2020-06-06 08:58:18 +02:00
Daniele Rondina
a19a1488bb Update Luet-lab/moby vendor/ 2020-06-06 08:30:28 +02:00
Daniele Rondina
d946e39a15 pkg/helpers/archive_test: Fix test 2020-06-05 23:53:33 +02:00
Daniele Rondina
a414b4ad4c contrib/config/luet.yaml: Add config_protect_confdir option 2020-06-05 23:40:36 +02:00
Daniele Rondina
415b1dab9a Add test of modifier 2020-06-05 23:12:35 +02:00
Daniele Rondina
16f717f04b tarModifierWrapperFunc: Fix config protect filename 2020-06-05 23:12:19 +02:00
Ettore Di Giacinto
9e0e1199df Check if image exists before skipping compilation 2020-06-03 21:00:30 +02:00
Daniele Rondina
8f0c528c08 Create helpers.UntarProtect for handle protected files
Currently, it's used the archive.ReplaceFileTarWrapper
that requite a []byte of the files replaced. This is not
a good idea if files are big and instead could be better
in the near future reimplement ReplaceFileTarWrapper with
a callback that return io.Reader instead of []byte.

If a protected file is already present on target rootfs
it is created a file with the same prefix used in Gentoo:

._cfgXXXX_<filename>
2020-06-02 11:08:37 +02:00
Daniele Rondina
a2231749ab Begin implementation for catch config protect files 2020-06-02 09:04:40 +02:00
Ettore Di Giacinto
990a5405cf Merge pull request #116 from mudler/review-config-path
Review configuration file parsing logic
2020-05-31 11:05:55 +02:00
Daniele Rondina
1d8a6174bb Drop duplicate code for general.same_owner 2020-05-30 16:51:10 +02:00
Daniele Rondina
341293c403 tests/integration/09_docker.sh: align new logic 2020-05-30 16:47:11 +02:00
Daniele Rondina
9e7c7e69f8 Review configuration file parsing logic
Luet support now these priorities on read configuration file:
- command line option (if available)
- $PWD/.luet.yaml
- $HOME/.luet.yaml
- /etc/luet/luet.yaml
2020-05-30 16:46:29 +02:00
Ettore Di Giacinto
86808ad49b Fixup dockerfile 2020-05-26 21:19:27 +02:00
Ettore Di Giacinto
d59cc42e22 Add target to create smaller binary 2020-05-26 21:07:18 +02:00
Ettore Di Giacinto
cc21e6fa5e Respect user-defined repository naming 2020-05-24 12:16:02 +02:00
geaaru
8c4f5b2911 Merge pull request #113 from mudler/annotations
Add Annotations to package spec
2020-05-23 10:52:32 +02:00
Daniele Rondina
44d68a9583 Add annotations option to package spec 2020-05-23 09:27:38 +02:00
Daniele Rondina
dba6c361c2 Move helpers/cli to cmd/helpers 2020-05-23 08:51:33 +02:00
Ettore Di Giacinto
4197d7af61 Add upgrade by using only the SAT core
- Adds upgrade --universe and upgrade --universe --clean. It will
  attempt to bring the system as much close as the content available in
  the repositories. It differs from a standard upgrade which checks
  directly that what is pulled in doesn't conflict with the system. In
  this new way, we just query the SAT solver to decide that on our
  behalf.
- Add uninstall --full-clean. It uses only the SAT solver to uninstall
  the package and it will drop as many packages as required (including
  revdeps of packages too.
2020-05-22 21:20:58 +02:00
Ettore Di Giacinto
bfde9afc7f Add Nodeps and Full options to upgrade 2020-05-22 21:20:57 +02:00
Ettore Di Giacinto
3237423dde Enhance upgrade output 2020-05-22 21:20:57 +02:00
Ettore Di Giacinto
ab179db96a Don't drop packages that would be re-installed during upgrade
Check for packages that are marked for deletion. If the ones that are
marked for install are depending on it, don't remove them at all
2020-05-22 21:20:52 +02:00
geaaru
916b2a8927 Merge pull request #112 from mudler/package-sanitized
Package sanitized
2020-05-20 11:42:47 +02:00
Daniele Rondina
a16bdddeb2 Add spectooling test suite 2020-05-20 10:26:30 +02:00
Daniele Rondina
e38a4b3d9b Use DefaultPackageSanitized struct for write specs 2020-05-20 09:59:48 +02:00
Ettore Di Giacinto
c52fe9a6b3 Add development version 2020-05-19 23:05:03 +02:00
Ettore Di Giacinto
956e55a1d4 Tag 0.7.9 2020-05-19 23:04:28 +02:00
Ettore Di Giacinto
9971fe9f45 Unique hashes for packages without deps 2020-05-18 19:57:01 +02:00
Daniele Rondina
11759f98e0 package/NewPackage: init Labels on AddLabel 2020-05-18 19:51:47 +02:00
Daniele Rondina
cb2ac15de8 package: Fix typo on labels key 2020-05-17 19:17:14 +02:00
Ettore Di Giacinto
c0b432befa Add development version 2020-05-16 22:46:29 +02:00
Ettore Di Giacinto
8e029a8ee4 Tag 0.7.8 2020-05-16 22:46:04 +02:00
Ettore Di Giacinto
51711dafba Add package_dir to pack a spec dir as the main artifact 2020-05-16 21:34:27 +02:00
Ettore Di Giacinto
2803430515 Add solver test scenario 2020-05-16 11:11:17 +02:00
Daniele Rondina
44213894bc Add commands aliases 2020-05-10 20:24:08 +02:00
Daniele Rondina
a7d1381cb5 cmd/create-repo: Add support for multiple trees 2020-05-10 20:18:10 +02:00
Daniele Rondina
2cb79c0071 cmd/build: Add support for multiple trees 2020-05-10 20:02:12 +02:00
Ettore Di Giacinto
7d17d3babf Merge pull request #109 from mudler/log-cmdline-opts
Log cmdline opts
2020-05-09 11:16:37 +02:00
Daniele Rondina
13df161fc6 logging: permit to disable color and emoji
Now it's possible disable color and emoji on standard
output with:

$> luet <cmd> --color=false --emoji=false

Hereinafter, the list of changes:

* Added logging option logging.color (default true)
* Added logging option logging.enable_emoji (default true)
* Added persistent flag --color
* Added persistent flag --emoji
2020-05-09 10:08:21 +02:00
Daniele Rondina
fe5ab9246f Added option for enable log to file.
Now it's possible logging to file is handled by the enable_logfile
option and by the path.

From cli is now possible:
* enable log to file with the option --enable-logfile
* modify the logfile path with the option --logfile/-l <path>
2020-05-09 10:05:34 +02:00
Daniele Rondina
993bcf9adf tree/validate: Add support for in memory cache with solver check 2020-05-08 20:05:21 +02:00
Ettore Di Giacinto
20cb96e0cc Simplify ordering check
we don't need a map[string]map[string]interface{}, as we don't need to
keep the data around
2020-05-04 17:34:29 +02:00
Daniele Rondina
b68634b58a solver: skip same packages in the order and avoid loop 2020-05-04 12:21:49 +02:00
Ettore Di Giacinto
1b529ea8c5 Add development version 2020-05-03 13:46:53 +02:00
Ettore Di Giacinto
2c2e6065d9 Tag 0.7.7 2020-05-03 13:46:35 +02:00
Ettore Di Giacinto
46014fb9c1 Enhance install output 2020-05-03 13:22:06 +02:00
Ettore Di Giacinto
ada9d886fa Enhance uninstall output 2020-05-03 13:22:00 +02:00
Ettore Di Giacinto
584b980644 Adapt integration test which requires full uninstall 2020-05-03 13:12:14 +02:00
Ettore Di Giacinto
a1d8ef1422 Allow to partially uninstall a package graph, make uninstall --full optional 2020-05-03 13:04:34 +02:00
Ettore Di Giacinto
7b6e4a2176 Add to the Solver the capability to check conflicts with revdeps 2020-05-03 10:34:18 +02:00
Ettore Di Giacinto
3befbfa915 Optimize uninstall computation 2020-05-02 15:43:57 +02:00
Ettore Di Giacinto
8dd756ec96 Add development version 2020-05-02 14:51:08 +02:00
Ettore Di Giacinto
1b6ffac7bd Tag 0.7.6 2020-05-02 14:50:29 +02:00
Ettore Di Giacinto
faef3d093a Add test fixtures 2020-05-02 14:03:34 +02:00
Ettore Di Giacinto
d4c25d74f5 Remove focus from test 2020-05-02 13:27:15 +02:00
Ettore Di Giacinto
2ed9781c88 Don't try to match against repo already installed packages 2020-05-02 13:24:46 +02:00
Ettore Di Giacinto
9d6d6bc0c8 Add more upgrade and reclaim tests scenarios 2020-05-02 12:18:19 +02:00
Ettore Di Giacinto
f8b2837741 Enhance reclaim output 2020-05-02 12:17:54 +02:00
geaaru
878e6d7b9c Merge pull request #103 from mudler/tmpdir-cleanup
Tmpdir cleanup
2020-05-02 09:20:01 +02:00
Daniele Rondina
c5b41946dc Add integration test for tmpdir cleanup 2020-05-02 08:43:25 +02:00
Daniele Rondina
a89c0af2f8 config/config_test: Fix typo 2020-05-01 15:34:17 +02:00
Daniele Rondina
60d9017952 CleanupTmpDir() is not Fatal 2020-05-01 15:27:19 +02:00
Daniele Rondina
1f99fde1c5 Add config test suite 2020-05-01 15:26:53 +02:00
Ettore Di Giacinto
4b63c9eaf9 Add test to be sure we don't index packages not in the tree 2020-05-01 11:48:05 +02:00
Ettore Di Giacinto
286d0fba2c Fix typo 2020-05-01 11:07:14 +02:00
Ettore Di Giacinto
624518bf77 Don't add package to the repository which aren't referenced by the tree 2020-05-01 10:52:40 +02:00
Daniele Rondina
51a4037b1b config: Initialize luet tmp basedir if doesn't exist 2020-05-01 08:18:18 +02:00
Ettore Di Giacinto
b13828c883 Add development version 2020-04-30 22:50:37 +02:00
Ettore Di Giacinto
886cbd0036 Tag 0.7.5 2020-04-30 22:50:12 +02:00
Ettore Di Giacinto
b91288153a Make tree validation cmd concurrent 2020-04-30 21:48:46 +02:00
Ettore Di Giacinto
9cb290b484 Improve database errors 2020-04-30 20:44:34 +02:00
Daniele Rondina
11944873ea Integrate tmpdir_base params and tmpdirs cleanup 2020-04-30 20:29:28 +02:00
Ettore Di Giacinto
322ac99f17 Annotate package runtime definition when reclaiming
This was an issue as we were copying the buildspec instead
2020-04-30 18:56:50 +02:00
Ettore Di Giacinto
6acc5fc97e Make BuildFormula support dependency cycles 2020-04-30 18:56:35 +02:00
Ettore Di Giacinto
6a4557a3b3 Make RequireContains support dependency cycles 2020-04-30 18:56:06 +02:00
Ettore Di Giacinto
fb3c568051 Add development version 2020-04-24 19:46:35 +02:00
Ettore Di Giacinto
14bc26ee22 Tag 0.7.4 2020-04-24 19:46:14 +02:00
Ettore Di Giacinto
18ccdbd6a9 Add --revdep to luet pkglist
Include also the path in the results
2020-04-24 19:05:47 +02:00
Ettore Di Giacinto
7d960b733d Add --revdep to luet search
Fixes #52
2020-04-24 19:05:24 +02:00
Ettore Di Giacinto
919b2c3cfc Don't print misleading messages to the user 2020-04-24 19:04:21 +02:00
Ettore Di Giacinto
a457c53824 Fixups to ExpandedRevdeps 2020-04-24 00:15:18 +02:00
Ettore Di Giacinto
8305d01e76 Walk requires in ExpandedRevdeps
In this way we annotate the visited and we avoid cycles that can be
generated by revdeps
2020-04-23 23:44:42 +02:00
Ettore Di Giacinto
ec32677dc1 Merge branch 'master' into develop 2020-04-22 22:17:49 +02:00
Ettore Di Giacinto
60619c36a3 Merge pull request #95 from mudler/develop (#96) 2020-04-22 22:16:58 +02:00
Ettore Di Giacinto
141f1bf773 Merge pull request #95 from mudler/develop
Merge develop
2020-04-22 22:15:34 +02:00
Ettore Di Giacinto
9321a310a5 Support mount with target folder in box exec
Also mount bind before pivotroot, this fixes bind mounting in general
2020-04-21 17:30:27 +02:00
Ettore Di Giacinto
4cee27da7f Fixup box CLI params and consume new box options 2020-04-20 23:03:03 +02:00
Ettore Di Giacinto
98876c8f20 Add support for env and hostmounts in box 2020-04-20 23:01:49 +02:00
Ettore Di Giacinto
4c2d38be59 Add package ExpandedRevdeps 2020-04-19 11:55:13 +02:00
Ettore Di Giacinto
20aa7c89f8 Use different result structs in pkglist 2020-04-19 11:37:23 +02:00
Ettore Di Giacinto
937609a9f4 Add machine readable output to pkglist 2020-04-19 11:24:41 +02:00
Ettore Di Giacinto
a3f0d848c9 Add builtime search to tree pkglist 2020-04-19 10:53:05 +02:00
Ettore Di Giacinto
8e91c255a3 Make FindPackageMatch match packages HumanReadableString 2020-04-19 10:47:55 +02:00
Ettore Di Giacinto
62381ed46d Structure search machine readable output 2020-04-19 10:31:07 +02:00
Ettore Di Giacinto
1171987ed3 Adapt integration test to search output change 2020-04-19 10:24:39 +02:00
Ettore Di Giacinto
ac871cb0a3 Add --output option to search
In such way it can be parsed by scripts more easily.
It also disable the spinner based on loglevel

Fixes #92
2020-04-18 23:22:00 +02:00
Ettore Di Giacinto
82cd0e17b6 Enhance search output 2020-04-18 22:51:27 +02:00
Ettore Di Giacinto
c972795a30 Tweak upgrade integration test
In this way we also test that the user configuration takes precedence
over the one advertized remotely
2020-04-18 17:35:58 +02:00
Ettore Di Giacinto
4d466d0330 Sync the repository data with user configured data
Fixup a regression, in this way we respect the user configuration in
regards to a repository to be consumed (priority and repository type)
regardless of the one advertized remotely.

Also add informative message about prio and type.
2020-04-18 17:33:46 +02:00
Ettore Di Giacinto
84407e5ae7 Add SetPriority to repository 2020-04-18 17:33:34 +02:00
Ettore Di Giacinto
845797a155 Merge pull request #93 from mudler/develop
Merge develop
2020-04-18 14:45:20 +02:00
Ettore Di Giacinto
83e19359a9 Update bbolt to 1.3.4
Includes fixes for Go 1.14

See: https://github.com/etcd-io/bbolt/issues/187
2020-04-18 13:58:14 +02:00
Ettore Di Giacinto
d77f875a8a Bump go version in travis 2020-04-18 12:41:23 +02:00
Ettore Di Giacinto
3963fb64e5 Update vendor 2020-04-18 11:49:28 +02:00
Ettore Di Giacinto
1d5ce53443 Add luet box command
It adds only the 'exec' subcommand to spawn processes in custom boxes

Relates to #45
2020-04-18 11:43:14 +02:00
Ettore Di Giacinto
a14f0abb5c Update vendor 2020-04-18 11:42:34 +02:00
Ettore Di Giacinto
64bac0823c Consume our moby fork
It handles #91
2020-04-18 11:41:34 +02:00
Ettore Di Giacinto
ee0fe1a86a Allow to finalizer to specify entrypoint command
In this way, finalizer in strict environment can override the default
shell used to run commands.

The shell keyword is a list, as it needs to contain the full command +
args.
2020-04-14 17:46:39 +02:00
Ettore Di Giacinto
333b9cc023 Add reclaim to CLI
Relates to #86
2020-04-13 19:33:30 +02:00
Ettore Di Giacinto
538ba8f5df Add luet reclaim
Reclaim allows to migrate between different system layouts. This
is a experimental feature (yet) and might be revisited in the future.

This change:

- Adds Reclaim(system) to Installer
- Adds unit tests

Relates to #86
2020-04-13 19:32:51 +02:00
Ettore Di Giacinto
eb56956c65 Add Touch file helper 2020-04-13 19:32:50 +02:00
Ettore Di Giacinto
32cf132a0c Drop downloadOnly bool from Installer cli options 2020-04-13 19:32:50 +02:00
Ettore Di Giacinto
4e2d42e397 Drop downloadOnly bool from installer.Install 2020-04-13 19:32:50 +02:00
Ettore Di Giacinto
c2eed1d999 Add quay.io badge 2020-04-13 18:11:02 +02:00
Daniele Rondina
1b8176777a cmd/search: Add filter to result 2020-04-13 15:50:45 +02:00
Ettore Di Giacinto
528f481a7b Annotate package files in metadata
Fixes #87
2020-04-13 10:52:41 +02:00
Ettore Di Giacinto
2f3eabc3ca Annotate git hash and time in displayed version 2020-04-12 14:37:49 +02:00
Ettore Di Giacinto
579a4f20fc Add docker fixture for integration tests 2020-04-11 20:12:43 +02:00
Ettore Di Giacinto
dd45a46ed0 Add docker from scratch integration test 2020-04-11 20:07:10 +02:00
Ettore Di Giacinto
f987ee9fa0 Handle errors from os/user
We use it to generate defaults, if error is found, we imply preserve
permissions
2020-04-11 20:05:56 +02:00
Ettore Di Giacinto
0fe2a8c950 Add development version 2020-04-11 18:30:11 +02:00
Ettore Di Giacinto
9704992c43 Tag 0.7.3 2020-04-11 18:29:46 +02:00
Ettore Di Giacinto
c2beab64cb Merge pull request #84 from mudler/develop
Merge develop
2020-04-11 18:24:02 +02:00
Ettore Di Giacinto
d8919f7250 Check if dependencies are selectors in validate 2020-04-10 20:00:37 +02:00
Daniele Rondina
42c380029c cmd/tree/validate: Fix order analysis 2020-04-10 17:41:27 +02:00
Daniele Rondina
69a82a1ca5 simpledocker: Use debug option for print container-diff results 2020-04-10 09:32:12 +02:00
Daniele Rondina
02c33896d5 simpledocker: Move warning before return 2020-04-10 09:31:08 +02:00
Daniele Rondina
c2e9176ab2 solver.Order now return error 2020-04-09 17:07:57 +02:00
Daniele Rondina
726a749b4b cmd/tree/validate: Integrate order of the solution 2020-04-09 15:58:05 +02:00
Ettore Di Giacinto
0d2668e452 Print warning if container-diffs return errors to stderr 2020-04-08 18:31:40 +02:00
Ettore Di Giacinto
77ba4193aa Add ValidateSelector to versioner interface and consume it
We can refactor furthermore by dropping the package methods, as now we
can consume a versioner in all places that requires it
2020-04-05 15:52:16 +02:00
Ettore Di Giacinto
5a5e7f1dfa Move version logic inside versioner
WIP, it needs yet to be under the interface implementation
2020-04-05 11:16:14 +02:00
Ettore Di Giacinto
cc0999b4c4 Merge pull request #83 from auto-maintainers/branch-be6698fa706d6ccfc3734b805b212f75175fd557
Update dependencies
2020-04-05 09:00:51 +02:00
MarvinHatesOceans
be6698fa70 Update dependencies 2020-04-04 23:56:14 +00:00
Ettore Di Giacinto
6540dcc99d Merge pull request #82 from mudler/packages_type
Add Packages type and Versioner interface
2020-04-04 17:28:49 +02:00
Ettore Di Giacinto
cf8091a7fd Re-enable debian versioning test, add more cases 2020-04-04 16:49:41 +02:00
Ettore Di Giacinto
3caaa01eb8 Add semver detection to versioner
In this way we compare only all version that are compliant to semver,
otherwise we fallback to debian sorting.
2020-04-04 16:48:48 +02:00
Ettore Di Giacinto
4eae0a851c Add versioner unit tests 2020-04-04 16:33:08 +02:00
Ettore Di Giacinto
626419f955 Fix original version mapping in versioner 2020-04-04 16:23:43 +02:00
Ettore Di Giacinto
5bf60deffc Update vendor 2020-04-04 15:35:45 +02:00
Ettore Di Giacinto
84625be9ac Adapt package.Best to take a Versioner interface 2020-04-04 15:33:14 +02:00
Ettore Di Giacinto
aaa8d8b7d6 Update gomod 2020-04-04 15:32:13 +02:00
Ettore Di Giacinto
04f9a88a5f Add versioner interface to wrap versioning operations
This allows to scope responsabilities and enables switching to different
ordering algorithms
2020-04-04 15:29:20 +02:00
Ettore Di Giacinto
9d58b0a0cc Adapt tests to refactor 2020-04-04 14:41:58 +02:00
Ettore Di Giacinto
5e31d940f0 Introduce Packages for []Package 2020-04-04 14:29:08 +02:00
Daniele Rondina
42d1ec5585 cmd/tree/bump: Add --to-stdout option 2020-04-04 13:42:25 +02:00
Daniele Rondina
07e78dd89b Update vendor github.com/Sabayon/pkgs-checker@076438c31739 2020-04-04 11:40:36 +02:00
geaaru
625a6ce773 Merge pull request #80 from mudler/versioning_integration
Versioning integration
2020-04-03 19:24:43 +02:00
Daniele Rondina
d6faa3335f tests/integration/08_versioning.sh: Add version with _ 2020-04-03 18:33:01 +02:00
Daniele Rondina
129ca8da55 Add version sanitized to Best() 2020-04-03 18:32:26 +02:00
Daniele Rondina
9e95d7d2a9 tests/integration/08_versioning.sh: Drop wrong assert 2020-04-03 15:24:56 +02:00
Daniele Rondina
e94674fca3 tests/integration/08_versioning.sh: Simplify test 2020-04-03 15:06:27 +02:00
Daniele Rondina
730b9a087a tests/integration/08_versioning.sh: Fix test 2020-04-02 19:39:31 +02:00
Ettore Di Giacinto
9cf6330146 Build empty packages in versioning fixture 2020-03-29 15:11:20 +02:00
Ettore Di Giacinto
daa0b815c1 Fixup assertEquals on versioning integration test 2020-03-29 14:52:14 +02:00
Ettore Di Giacinto
1779382811 Extend tree validate checks 2020-03-29 14:52:13 +02:00
Ettore Di Giacinto
c705728a2a Build specific package, it triggers version search 2020-03-29 14:52:12 +02:00
Ettore Di Giacinto
15e772d96d Fixup versioning of fixture 2020-03-29 14:52:10 +02:00
Ettore Di Giacinto
0d6dc49a95 Add versioning integration test 2020-03-29 14:52:00 +02:00
Daniele Rondina
8df76c8941 Update vendor github.com/Sabayon/pkgs-checker@fd529912510d 2020-03-29 14:04:55 +02:00
Ettore Di Giacinto
b1ce8d2eba Merge pull request #77 from auto-maintainers/branch-830fdb5bf299f959bcd02fe577c19471daa229cd
Update dependencies
2020-03-29 12:15:14 +02:00
Daniele Rondina
ee66839bd3 pkg/package/version: Better search for last + 2020-03-29 09:10:52 +02:00
Daniele Rondina
9b61ed9e1d Fix panic if luet is executed without args 2020-03-29 08:48:51 +02:00
MarvinHatesOceans
d59fff0740 Update dependencies 2020-03-28 23:19:43 +00:00
Ettore Di Giacinto
aeb10338f6 Add development version 2020-03-28 17:32:06 +01:00
Ettore Di Giacinto
02653e03d8 Tag 0.7.2 2020-03-28 17:31:35 +01:00
Ettore Di Giacinto
2c48fe0524 Merge pull request #76 from mudler/box_finalizers
Add Box finalizers
2020-03-28 17:30:09 +01:00
Ettore Di Giacinto
a0113dcd13 Drop verbose output 2020-03-28 17:05:23 +01:00
Ettore Di Giacinto
d45536505b Add integration test for box finalizers 2020-03-28 16:59:37 +01:00
Ettore Di Giacinto
92d335d5d1 Add exec command to cli
It is the entrypoint for box, to run commands with unshare
2020-03-28 16:58:59 +01:00
Ettore Di Giacinto
49d7b4e2bf Add box structure to handle containerized executions 2020-03-28 16:58:46 +01:00
Ettore Di Giacinto
f15ed3fda1 Move finalizer in its own struct
Also distinguish when we run in a root target dir ("/") or when we want
to run the commands in a separate folder
2020-03-28 16:57:40 +01:00
Ettore Di Giacinto
2ad16fa875 Add ListDir helper 2020-03-28 16:55:16 +01:00
Ettore Di Giacinto
416be23a46 Don't always remove unpacked repository content 2020-03-28 12:07:40 +01:00
Daniele Rondina
98c7d5c450 cmd/create-repo: Fix default value of tree-compression 2020-03-27 08:59:52 +01:00
Daniele Rondina
50091b2a4b pkg/installer: Add test for uncompress tree and minor cleanup 2020-03-25 00:19:52 +01:00
Ettore Di Giacinto
0067fa82a5 Merge pull request #75 from mudler/fixup_upgrade
Don't attempt to upgrade packages already in system
2020-03-24 21:14:53 +01:00
Ettore Di Giacinto
117554792d Don't attempt to upgrade packages already in system 2020-03-24 20:30:59 +01:00
Daniele Rondina
d0b7552aca tests: Align test to new args 2020-03-24 20:01:22 +01:00
Daniele Rondina
454e9d934e Use filename instead of name on repo specs 2020-03-24 19:40:11 +01:00
Ettore Di Giacinto
dd91a61caf Merge pull request #74 from mudler/split_download
Split download
2020-03-24 19:02:16 +01:00
Ettore Di Giacinto
bc5d01c3df Add integration test for search of installed packages 2020-03-24 18:19:09 +01:00
Ettore Di Giacinto
af7f1de9f1 Split download and installer worker
Also, drop downloadOnly from install
2020-03-24 18:19:09 +01:00
Ettore Di Giacinto
3f5aa7db22 Merge pull request #73 from mudler/refactor-repository
Refactor repository
2020-03-24 17:44:37 +01:00
Daniele Rondina
a0f9222068 Add check of repository metafile archive in tests 2020-03-24 14:01:22 +01:00
Daniele Rondina
520768d0ca pkg/installer: Align tests with new default 2020-03-24 12:52:45 +01:00
Daniele Rondina
4f002ab40f pkg/installer/repository: The check for repo_files is not needed for parsing normal repo 2020-03-24 12:52:24 +01:00
Daniele Rondina
60635a03eb Fix propagation of repo file name and add test for meta compressed 2020-03-24 00:31:41 +01:00
Daniele Rondina
202ed2651a 💥 Refactor and split repository.yaml file 2020-03-24 00:05:16 +01:00
Daniele Rondina
4e461fd6be cmd/repo/update: Update only enabled repo 2020-03-23 23:40:27 +01:00
Daniele Rondina
6bd8fe6789 Add omitempty to DefaultPackage fields 2020-03-22 22:23:11 +01:00
Ettore Di Giacinto
7cf6d51355 Tweak ginkgo parameters
- Add flakeAttempts
- Add race detection to CI runs
2020-03-22 22:07:46 +01:00
Ettore Di Giacinto
d5166c55ab Lock only on commands that aren't meant to run in parallel 2020-03-22 15:19:08 +01:00
Ettore Di Giacinto
7de5f6656d Merge branch 'develop' 2020-03-22 14:42:51 +01:00
Ettore Di Giacinto
9e62111e1a Add development version 2020-03-22 14:42:35 +01:00
Ettore Di Giacinto
83cfcd878d Tag 0.7.1 2020-03-22 14:42:04 +01:00
Ettore Di Giacinto
578b323e1b Merge pull request #72 from mudler/speedup_travis
Speedup travis
2020-03-22 14:41:19 +01:00
Ettore Di Giacinto
d7b503d4a5 Drop verbosity in ginkgo params 2020-03-22 14:41:19 +01:00
Ettore Di Giacinto
fcdaf9338e Drop unneeded dependencies 2020-03-22 14:41:19 +01:00
Ettore Di Giacinto
adeb973c31 Make test-coverage in one step, deploy release only on tags 2020-03-22 14:41:15 +01:00
Ettore Di Giacinto
655da7e883 Merge pull request #72 from mudler/speedup_travis
Speedup travis
2020-03-22 14:40:34 +01:00
Ettore Di Giacinto
8dbc266b39 Drop verbosity in ginkgo params 2020-03-22 13:52:07 +01:00
Ettore Di Giacinto
5942e2f20c Drop unneeded dependencies 2020-03-22 11:00:30 +01:00
Ettore Di Giacinto
1a8fb77771 Make test-coverage in one step, deploy release only on tags 2020-03-22 10:55:04 +01:00
Daniele Rondina
40830ecf42 installer/repository.go: Use switch with search options 2020-03-22 10:46:51 +01:00
Ettore Di Giacinto
b2ba17f7f7 Add development version 2020-03-21 21:43:21 +01:00
Ettore Di Giacinto
3c5e144b8d Tag 0.7 2020-03-21 21:39:12 +01:00
Ettore Di Giacinto
44aa69928a Merge pull request #69 from mudler/add-pkg-labels
Added `labels` to package definition
2020-03-21 21:37:42 +01:00
Daniele Rondina
c7652c8a70 Added labels to package definition
* cmd/search: Add support for search of the packages
  with a specific label.

* review Search method of Repositories for permit
  different search modes.

* labels are k/v attributes and could be matched
  through label key (with HasLabel method) or through
  regex that use "$key" + "=" + "$value"
2020-03-21 19:24:27 +01:00
Ettore Di Giacinto
fab2d21ac1 Drop unused function 2020-03-19 22:39:22 +01:00
Ettore Di Giacinto
487239334f Treat base case with same algorithm of dependencies 2020-03-19 22:37:39 +01:00
Ettore Di Giacinto
69477a0c36 Make uniform build of seeds and packages 2020-03-19 22:34:26 +01:00
Ettore Di Giacinto
67362a3bc1 Drop unused condition 2020-03-19 22:29:34 +01:00
Ettore Di Giacinto
302d18e749 Tag the target image with the computed hash 2020-03-19 22:28:33 +01:00
geaaru
9047e13d21 Merge pull request #68 from mudler/tree-bump
Tree bump
2020-03-16 22:57:14 +01:00
Daniele Rondina
0249c0fa4a version: Add temporary workaround for handle build string with _ 2020-03-16 22:18:48 +01:00
Daniele Rondina
fc40c770ab Add 'tree bump' command 2020-03-16 22:18:48 +01:00
Ettore Di Giacinto
4357ee45e9 Drop unused functions in package 2020-03-16 18:08:46 +01:00
Daniele Rondina
6ce9a86245 Update vendor github.com/Sabayon/pkgs-checker@b6efed54b4b1 2020-03-16 00:37:31 +01:00
Daniele Rondina
853a1995b4 cmd/tree/validate: Now support multiple tree paths 2020-03-15 19:16:22 +01:00
Daniele Rondina
039b77fdfd cmd/tree/validate: Add counter of broken deps 2020-03-15 17:40:20 +01:00
Ettore Di Giacinto
050d9b3095 Adapt to image based on debian instead of alpine for fixture 2020-03-15 15:30:27 +01:00
Ettore Di Giacinto
4985ff7b5a Use standard golang image for fixture (alpine doesn't contain gcc) 2020-03-15 15:42:24 +01:00
Ettore Di Giacinto
6f138811dd Make recipes idempotent, allowing to load multiple trees 2020-03-15 13:31:12 +01:00
Daniele Rondina
f02c54a274 cmd/tree/validate: Improve scripting integration 2020-03-14 17:38:57 +01:00
Daniele Rondina
d6182cba3b Add cli command 'tree validate' 2020-03-14 11:14:14 +01:00
Daniele Rondina
bc5c0fa0cf cmd/tree/pkglist: Cleanup code 2020-03-14 11:14:14 +01:00
Ettore Di Giacinto
dc64dbff75 Don't try to solve finalizers with --nodeps 2020-03-13 19:32:00 +01:00
Daniele Rondina
a94e430a3b cmd/tree/pkglist: Add --matches/-m flag 2020-03-13 18:08:49 +01:00
Daniele Rondina
93f8f0dd0f solver: Add category to tests 2020-03-12 00:26:53 +01:00
Daniele Rondina
7bdcc72dd3 Update vendor github.com/Sabayon/pkgs-checker@f4e0aec412f0 2020-03-11 08:38:15 +01:00
Daniele Rondina
e47c07d438 Update vendor github.com/Sabayon/pkgs-checker@935615ba9d27 2020-03-10 09:12:22 +01:00
Daniele Rondina
6fc5d1a97b Upgrade vendor github.com/Sabayon/pkgs-checker@v0.6.1 2020-03-09 23:35:01 +01:00
Daniele Rondina
f4a5a97cff tree/compiler_recipe: Return right error message 2020-03-08 16:28:12 +01:00
Daniele Rondina
8c972403a3 tree/compiler_recipe: Handle invalid tree path
If instead of abs path is used bash annotation
now return an error related to wrong directory
supply.
2020-03-08 15:47:40 +01:00
Daniele Rondina
82a17a1a22 Drop cfgFile print to improve bash integration 2020-03-08 00:13:45 +01:00
Daniele Rondina
17238d187a Change 'Skip dir' message to debug 2020-03-08 00:07:20 +01:00
Daniele Rondina
ad011e937f cmd/tree/pkglist: Fix init of regExcludes 2020-03-06 17:54:26 +01:00
Daniele Rondina
25c796c430 Add 'tree pkglist' command cli 2020-03-06 17:51:04 +01:00
Daniele Rondina
df5499393f --verbose/-v is now --debug/-d 2020-03-06 16:53:36 +01:00
Ettore Di Giacinto
026ddf6fc9 Add dev version tag 2020-03-05 18:37:16 +01:00
Ettore Di Giacinto
ec0b83a811 Prepare for 0.6.4 tag 2020-03-05 18:36:51 +01:00
Ettore Di Giacinto
765261f233 Add Swap() to Installer. Refactor code in Upgrade() to consume it 2020-02-27 23:48:14 +01:00
Ettore Di Giacinto
6c7e24fadf Sync repositories once (and at beginning) during upgrade 2020-02-27 23:25:29 +01:00
Ettore Di Giacinto
de23e0d5b1 Download dependencies before installing
This enables smoother upgrades, and uses cache to pre-download
packages before uninstall/install

Allows also from cli to download deps only
2020-02-27 23:15:31 +01:00
Ettore Di Giacinto
8572aa5222 Preserve cache data from deletion during uninstall 2020-02-27 23:14:36 +01:00
Ettore Di Giacinto
0e0c2f21a6 Add dev version tag 2020-02-27 18:57:41 +01:00
Ettore Di Giacinto
575079bb77 Prepare for 0.6.3 tag 2020-02-27 18:57:13 +01:00
Ettore Di Giacinto
5bcc8d112a Enforce solver constraints
- Don't sign installed packages during finalizer execution
- Enforce solver constraints: build ALO and AMO rules taking into account
  that the current package might not be selected at all.
- Force uninstalls on upgrade
- Enable option to tell uninstall to ignore conflict with the analized system state,
  as we don't want any conflict with the installed to raise during the upgrade.
  In this way we both force uninstalls and we avoid to check with conflicts
  against the current system state which is pending to deletion.
  This is due to the fact that now the solver enforces the constraints
  and explictly denies two packages of the same version installed.
- Adapt test as now we generate more constraints, which makes the solver more
  noisy on the package that are explictly selected or not
2020-02-27 18:38:31 +01:00
Ettore Di Giacinto
65b17f5283 Add simple upgrade integration test 2020-02-26 23:59:48 +01:00
Ettore Di Giacinto
a0618107a8 Adapt tests 2020-02-26 23:56:21 +01:00
Ettore Di Giacinto
25f2abf103 Do conflict with same package versions while building formula 2020-02-26 23:01:08 +01:00
Ettore Di Giacinto
e3ebfd6bfe Respect ImageRepository with build --nodeps 2020-02-26 19:00:22 +01:00
Ettore Di Giacinto
48df98bcae Add dev version tag 2020-02-23 22:42:02 +01:00
Ettore Di Giacinto
0ce1baa448 Prepare for 0.6.2 tag 2020-02-23 22:41:33 +01:00
Ettore Di Giacinto
07610bc216 Add build option to remove backend artifacts
Add keep-exported-images to build to instruct to remove backend artifacts (docker images)
2020-02-21 22:33:18 +01:00
Daniele Rondina
a04dadf100 Add support for parsing version with build 2020-02-21 21:56:32 +01:00
Daniele Rondina
2f4ce42472 Update vendor github.com/Sabayon/pkgs-checker 2020-02-21 21:55:42 +01:00
Daniele Rondina
def04724d4 Add support for build identifier on version 2020-02-20 17:46:47 +01:00
Ettore Di Giacinto
38296bc5d7 Add test for HashFingerprint() 2020-02-19 18:33:14 +01:00
Ettore Di Giacinto
7e16e0cdb6 Add HashFingerprint() to Package and consume it while generating image names
In this way we don't depend on the package fingerpint characters while generating images, allowing
to support completely semver (e.g. + character is not allowed in docker images tag).

This change is not backward compatible with images currently already built and will invalidate the cache
2020-02-19 18:28:29 +01:00
Ettore Di Giacinto
647ea35983 Add a way to extend the CLI with external binaries
This lets luet to be extended like git does (just by having luet-subcommand binaries under $PATH)
following the UNIX filosophy. It has the downside that we silence the errors from cobra and we handle
them by ourselves.

Also the Exec syscall is not portable, and it should have implementation for each platform (hence why in helpers)
2020-02-18 23:08:14 +01:00
Ettore Di Giacinto
4648dedb55 Add --nodeps, --onlydeps and --force to Installer
This allows also to use it on upgrade and uninstall as it exposes the Installer options to the CLI.

See #48, #49
2020-02-18 18:37:56 +01:00
Ettore Di Giacinto
2ce427d601 Add Nodeps, Onlydeps and Force options to Compiler
See #41
2020-02-18 18:36:44 +01:00
Ettore Di Giacinto
b01c017507 Add HumanReadableString() to Package 2020-02-18 18:20:45 +01:00
Ettore Di Giacinto
189c042fea Add dev version tag 2020-02-17 18:04:36 +01:00
Ettore Di Giacinto
4e675723e8 Prepare for 0.6.1 tag 2020-02-17 18:04:05 +01:00
Ettore Di Giacinto
dd00d491b9 Skip repository if no candidate is found
FindPackageCandidate is idempotent and returns the same definition if no new is found.
This prevents installs from multiple-repos
2020-02-17 17:21:31 +01:00
Ettore Di Giacinto
26b94888c3 Skip image building if we pulled them successfully 2020-02-15 22:53:00 +01:00
Ettore Di Giacinto
01e635bd78 Add dev version tag 2020-02-15 16:52:30 +01:00
Ettore Di Giacinto
68a5604d8c Prepare for 0.6 tag 2020-02-15 16:51:51 +01:00
Ettore Di Giacinto
fcec6c5699 Add Push to hub feature
Add to the backends the Push capabilities to push images that were correctly built.
Also expose them to the CLI, along with KeepImg which was hidden.
2020-02-15 14:45:05 +01:00
Ettore Di Giacinto
d527eaed60 Allow to override default image repository 2020-02-15 14:31:23 +01:00
Ettore Di Giacinto
c7253ac8ad Adapt test to spec changes 2020-02-14 08:04:26 +01:00
Ettore Di Giacinto
fffed79767 Test uninstall without explicit version
By dropping the selector in the simple integration test
2020-02-13 14:17:54 +01:00
Ettore Di Giacinto
f329e1d5e0 Add retrieve to compilation spec
It allows to copy build artifact during buildtime. In this way
artifacts between different seed images can be shared

```
retrieve:
- a-test-1.0.package.*
- https://...
```

They will be available under WORKDIR
2020-02-13 14:15:43 +01:00
Ettore Di Giacinto
20bc250470 Add dev version tag 2020-02-12 15:45:01 +01:00
Ettore Di Giacinto
05b13c6c1e Prepare for 0.5.1 tag 2020-02-12 15:44:34 +01:00
geaaru
0fb3497a3b Merge pull request #56 from mudler/gorl
Add resolvers to ehnance solver's heuristic
2020-02-12 15:39:56 +01:00
Ettore Di Giacinto
3563636ea9 Update config example in contrib/config/luet.yaml 2020-02-12 15:16:33 +01:00
Ettore Di Giacinto
d6cdb8ea42 Update README 2020-02-12 14:33:59 +01:00
Ettore Di Giacinto
d7d05de9fe Add integration test for qlearning solver 2020-02-12 13:58:28 +01:00
Ettore Di Giacinto
a756c802f9 Hook config when generating the SolverOptions, display debug message about solver type 2020-02-12 12:24:07 +01:00
Ettore Di Giacinto
d6f7c47eae Add compact string notation for SolverOptions 2020-02-12 12:23:37 +01:00
Ettore Di Giacinto
b2a5de9222 Adapt installer test to constructor changes 2020-02-12 12:05:28 +01:00
Ettore Di Giacinto
3cd87abafe Consume SolverOptions in cli 2020-02-12 11:23:38 +01:00
Ettore Di Giacinto
7e388c6fed Cleanup resolver from logger
It creates cycle and we don't want to output anything from the computation process.
We should handle output in different stages

Also create constructor for solver to be able to consume resolvers.
2020-02-12 11:22:56 +01:00
Ettore Di Giacinto
07a154474b Consume SolverOptions in compiler 2020-02-12 11:21:55 +01:00
Ettore Di Giacinto
6aa353edb2 Consume SolverOptions in installer 2020-02-12 11:21:30 +01:00
Ettore Di Giacinto
8951203165 Add LuetSolverOptions to tweak solver parameter in Config 2020-02-12 11:21:08 +01:00
Ettore Di Giacinto
d330fedcc4 fmt 2020-02-12 11:05:13 +01:00
Ettore Di Giacinto
dfb6dab9dc Move repository helpers under config
They are generated after the system config, let the structure provide such information
2020-02-12 10:20:07 +01:00
Ettore Di Giacinto
4f33eca263 Consume internal attempts in QLearn so resolver can be re-used 2020-02-12 09:22:10 +01:00
Ettore Di Giacinto
54b0dce54b Respect argument passed to Untar in helpers 2020-02-11 16:55:00 +01:00
Ettore Di Giacinto
ea2a60a853 Cleanup, drop hardcoded values and use constructors 2020-02-11 15:58:28 +01:00
Ettore Di Giacinto
c8f4ba0a47 Add Factorial helper 2020-02-11 15:00:28 +01:00
Ettore Di Giacinto
33da68c2ff update vendor/ 2020-02-11 15:00:14 +01:00
Ettore Di Giacinto
c9090ef1fd Bump gophersat
Pin to master which now includes https://github.com/crillab/gophersat/pull/17
2020-02-11 14:59:50 +01:00
Ettore Di Giacinto
7e0ea34b81 Switch back to gophersat 2020-02-11 14:58:17 +01:00
Ettore Di Giacinto
2f6bef14d5 Revert "update vendor/"
This reverts commit 7ce522110e.
2020-02-11 14:55:49 +01:00
Ettore Di Giacinto
711c039296 Reward by observedDelta
Keep a record of the observed delta and maximize reward for it.
Also add Noop actions which is turned off by default.

Let finish the execution also when no solution is found, as we will take the
minimum observed delta as result.

This is done on purpose to avoid guessing "when" is a good time to stop the agent,
as it could be in the middle of picking up a new action which is not the final
(but we need limits, we can't let it run forever).
2020-02-11 14:52:24 +01:00
Ettore Di Giacinto
7ce522110e update vendor/ 2020-02-11 09:52:38 +01:00
Ettore Di Giacinto
ac6554c291 Support Add/Removal actions
Also keep the list of the wanted targets as we walk it. We will let the agent to try different solutions
2020-02-11 09:51:52 +01:00
Ettore Di Giacinto
d4255b086b Uncomment failing test
Add test to support that normally we need to pass explictly the installable ones only.
2020-02-11 09:50:26 +01:00
Ettore Di Giacinto
1b90407475 Don't preclude action space 2020-02-11 09:21:25 +01:00
Ettore Di Giacinto
6f6e2bf15f Pin to gophersat version
Having the same var in the and block seems to make gophersat crash. Even if might be unoptimal,
we need this to tighten the conditions between packages.

Switch to gophersat fork until this fix is merged upstream:

https://github.com/crillab/gophersat/pull/17
2020-02-11 09:06:57 +01:00
Ettore Di Giacinto
6d450d3af0 Add simple QLearning solver support
Still experimental, it covers very small subset of action domains (just removal from target).

Added a pending test that currently fails
2020-02-10 17:18:18 +01:00
Ettore Di Giacinto
33b442a832 Add accessor to decode from package String() 2020-02-10 17:14:46 +01:00
Ettore Di Giacinto
f068bfdb9b Add PackageResolver to add heuristics on unsat solutions 2020-02-10 09:41:09 +01:00
Ettore Di Giacinto
4dc4205868 Drop json skip from id. It prevents storm to track entities #55 2020-02-08 18:18:05 +01:00
Ettore Di Giacinto
50ec17e738 Bump version for upcoming release 2020-02-08 12:08:31 +01:00
Ettore Di Giacinto
4c80d70512 Prepare for 0.5 tag 2020-02-08 12:07:18 +01:00
Ettore Di Giacinto
b826288037 Update README 2020-02-08 12:06:54 +01:00
Ettore Di Giacinto
821ac20fa2 Highlight Documentation link 2020-02-06 18:30:33 +01:00
Ettore Di Giacinto
97edac4aa1 README Fixups 2020-02-06 18:26:43 +01:00
Ettore Di Giacinto
a118c7f98b Merge branch 'develop' 2020-02-06 18:24:47 +01:00
Ettore Di Giacinto
fcd05a57d3 Update README 2020-02-06 18:24:15 +01:00
Ettore Di Giacinto
255aecf20b Merge pull request #44 from mudler/develop
Merge develop
2020-02-04 20:59:35 +01:00
Ettore Di Giacinto
5594844971 Adapt integration test to test install and build from cli without explicit versioning 2020-02-04 20:16:35 +01:00
Ettore Di Giacinto
f813370501 Resolve selectors if present before starting install 2020-02-04 20:15:59 +01:00
Daniele Rondina
c353ab4978 client/http: Add support for authentication Basic/Token 2020-02-03 00:58:55 +01:00
Daniele Rondina
1653a60428 cmd/create-repo: Permit to use repository from config 2020-02-02 11:29:05 +01:00
Daniele Rondina
de2afe8ed0 tests/integration: Add cleanup command 2020-02-02 10:38:41 +01:00
Daniele Rondina
298de447b8 Fix installer/client test suite 2020-02-01 21:18:32 +01:00
Daniele Rondina
40687c3072 Use dynamic pkgs cache dir for test suite 2020-02-01 20:05:19 +01:00
Daniele Rondina
2528fe0fb4 Add cleanup command 2020-02-01 19:35:46 +01:00
Daniele Rondina
78b5963a4f Add support for local packages cache 2020-02-01 19:35:30 +01:00
Daniele Rondina
524bbf990e Add support for same-owner config option 2020-02-01 19:01:15 +01:00
Ettore Di Giacinto
96f4a6c0e3 Add serve-repo for a minimal embedded micro http server 2020-01-31 21:22:15 +01:00
Ettore Di Giacinto
0e30e6a1ad Treat single conflict cases as well when building conflict formula 2020-01-31 20:50:55 +01:00
Ettore Di Giacinto
0147b2cf99 Don't cleanup the whole cache download dir, just the downloaded artifact
Otherwise whatever we download cannot be consumed (e.g. tree).
Instead whoever is calling the client should care to cleanup the returned artifact
2020-01-30 18:03:25 +01:00
Ettore Di Giacinto
eee0136156 Add compressed tree integration test 2020-01-28 17:47:42 +01:00
Ettore Di Giacinto
c6fe34b059 Support compression and checksum for trees
Fixes #33 #34 #35
2020-01-28 17:46:32 +01:00
Daniele Rondina
7ad767a81b cmd/repo: Add update command 2020-01-12 23:36:08 +01:00
Daniele Rondina
4c5f6f9f8d cmd/repo/list: View revision on cached repositories 2020-01-12 23:36:08 +01:00
Daniele Rondina
c9b684523f installer: Add support for cached repository 2020-01-12 23:35:58 +01:00
Daniele Rondina
aeea0cc5fe Add Cached option to LuetRepository 2020-01-12 23:33:19 +01:00
Daniele Rondina
850b3f1c50 contrib: Update luet.yaml example 2020-01-12 23:33:19 +01:00
Daniele Rondina
091e51e426 logger: Add support for custom color text 2020-01-12 23:33:19 +01:00
Ettore Di Giacinto
f498dfc692 Merge pull request #42 from mudler/develop
Merge develop into master
2020-01-06 20:22:55 +01:00
Ettore Di Giacinto
22bc53ba13 Merge branch 'master' into develop 2020-01-06 19:14:17 +01:00
Ettore Di Giacinto
b6dba27a4a Add 0.5 dev tag 2020-01-06 19:12:54 +01:00
Ettore Di Giacinto
12c97c7a2a Prepare for 0.4 tag 2020-01-06 19:11:44 +01:00
Ettore Di Giacinto
f5e7c2ad92 Allow more matches, we select the best one anyway 2020-01-06 11:33:35 +01:00
Daniele Rondina
07633dc307 Add container-diff summary 2020-01-05 18:05:56 +01:00
Ettore Di Giacinto
d5fd14bceb Merge branch 'build_clean' into develop 2020-01-05 16:27:31 +01:00
Ettore Di Giacinto
d2abaa9cc1 Add message asserting skip. Also return pointer to allow to edit Options 2020-01-05 16:26:42 +01:00
Ettore Di Giacinto
d23e1dee78 Default to Clean true in CompilerOptions 2020-01-05 16:08:40 +01:00
Ettore Di Giacinto
ee055e08b1 Set abs path when returning artifact from yaml 2020-01-05 16:08:39 +01:00
Ettore Di Giacinto
6d745ef915 Add build --clean to CLI 2020-01-05 16:08:39 +01:00
Ettore Di Giacinto
02c37c7451 Fix tests after contructor change 2020-01-05 16:08:39 +01:00
Ettore Di Giacinto
1d1efad18b Skip building if artifact already exists 2020-01-05 15:30:16 +01:00
Ettore Di Giacinto
bcc6ce19ea Move compiler options to its own struct
Also add Clean attribute, to handle a future build clean method
2020-01-05 14:32:26 +01:00
Daniele Rondina
9b6f4a094d Use const for repository.yaml and tree.tar 2020-01-05 00:40:28 +01:00
Daniele Rondina
e013412832 Add repo list command 2020-01-04 17:56:19 +01:00
Daniele Rondina
60ed9e0a04 cmd/root: Move message about config file used to debug 2020-01-04 17:53:15 +01:00
Daniele Rondina
e751b989e0 Fix installer_tests 2020-01-04 16:15:56 +01:00
Daniele Rondina
6012e0081e Add support of incremental revision for repos
* on repository creation now if the repository.yaml
  is already present, the current revision is used.

* add --reset-revision option for force reset revision
  of a specific repository
2020-01-04 00:31:11 +01:00
Daniele Rondina
d3bd78d618 Repository now support Revision and LastUpdate 2020-01-03 20:09:29 +01:00
Daniele Rondina
6af62b5851 Define default values for loaded repositories 2020-01-03 19:37:51 +01:00
Daniele Rondina
7ec36da059 Add --fatal option
Close #8
2020-01-03 15:41:45 +01:00
Daniele Rondina
c284d3e4bf Add support for logging to file 2020-01-03 15:22:55 +01:00
Daniele Rondina
f28e8deb96 logger: Add support for json format 2020-01-03 15:20:42 +01:00
Daniele Rondina
4433fc72ac Add vendor go.uber.org/zap 2020-01-03 15:20:02 +01:00
Daniele Rondina
20654d5dbb contrib: Update config example 2020-01-03 10:05:38 +01:00
Daniele Rondina
b986c613ab tests/integration/01_simple.sh: Use system-dbpath/system-target from config 2020-01-02 18:39:14 +01:00
Daniele Rondina
cd0e588fa9 cmd/config: Align print to new tags 2020-01-02 18:38:07 +01:00
Daniele Rondina
5358475069 test/integration/01_simple.sh: Use boltdb engine on config 2020-01-02 18:31:56 +01:00
Daniele Rondina
716d404307 Align logic of cache repositories in all commands 2020-01-02 18:31:25 +01:00
Daniele Rondina
b12410edb7 Errors on create database paths are now fatal 2020-01-01 22:58:53 +01:00
Daniele Rondina
db7301f7bf Align selector logic for explicit version with PkgSelectorConditionFromInt 2020-01-01 22:58:19 +01:00
Daniele Rondina
ebcf6075d0 database_mem: Add test for specific candidate 2020-01-01 22:55:19 +01:00
Daniele Rondina
98248432d1 Update pkgs-checker vendor/ 2020-01-01 22:44:18 +01:00
Ettore Di Giacinto
bbd811a6f2 If version is explict, do not add the selector in it 2020-01-01 14:20:45 +01:00
Daniele Rondina
9af733370a Update integration-test 2020-01-01 13:44:39 +01:00
Daniele Rondina
ce888a2f40 Fix marshal of repository and use of cache repos 2020-01-01 13:44:39 +01:00
Daniele Rondina
01e66ee0b4 Review install phase 2020-01-01 13:44:39 +01:00
Daniele Rondina
a71e1a6f1d Integrate config.LuetRepository with installer
* installer.LuetRepository is now installer.LuetSystemRepository,
  a struct that extend config.LuetRepository

* config: system_repositories option is now "repositories".
  This resolve viper issue.

* config: cache_repositories option is now "repetitors"
  This resolve viper issue.

* cmd/*: Now use new config.LuetRepository

* cmd/search: now create local luet repository if database_engine
  is equal to "boltdb"
2020-01-01 13:43:23 +01:00
Daniele Rondina
3b266fd600 installer: Repositoris now support multiple uris 2020-01-01 13:43:23 +01:00
Daniele Rondina
0d02eccc6c installer: Fix tests 2020-01-01 13:43:23 +01:00
Daniele Rondina
ee210851f0 contrib: Add luet config stub 2020-01-01 13:43:23 +01:00
Daniele Rondina
7f160a7a89 config: Add system section 2020-01-01 13:43:23 +01:00
Daniele Rondina
8b66127016 cmd/install: Now use _gentoo.ParsePackageStr and support pkgs without version 2020-01-01 13:43:22 +01:00
Daniele Rondina
16453bd09f Concurrency option is now global
* concurrency could be configured now from cmdline,
  configuration file or LUET_GENERAL__CONCURRENCY env variable

* verbose option is now related to debug option
2020-01-01 13:43:22 +01:00
Daniele Rondina
358b39b5dd simpledocker: Support show_build_output option 2020-01-01 13:43:22 +01:00
Daniele Rondina
da11a84d23 Rename repository type local to "disk"
See reference #4
2020-01-01 13:43:22 +01:00
Daniele Rondina
0cb49a40c0 Support logging level and spinner customization 2020-01-01 13:43:22 +01:00
Daniele Rondina
bbc9574745 Review luet configuration file 2020-01-01 13:43:22 +01:00
Daniele Rondina
6f837c8c26 cmd/uninstall: Support uninstall of multiple pkgs 2020-01-01 13:43:21 +01:00
Daniele Rondina
4dffc658db database_boltdb: Align review of version comparision logic 2020-01-01 13:43:21 +01:00
Daniele Rondina
a7e262cc48 cmd/build: Now support package without version/selector 2020-01-01 13:43:21 +01:00
Daniele Rondina
91d05b071d Review version comparision logic 2020-01-01 13:43:21 +01:00
Daniele Rondina
bbeb800611 cmd/build: selector is defined inside version 2020-01-01 13:43:21 +01:00
Daniele Rondina
ffcac1d03e Update vendor pkgs-checker 2020-01-01 13:43:21 +01:00
Daniele Rondina
4c62f714c4 cmd/build: Now use _gentoo.ParsePackageStr 2020-01-01 13:43:17 +01:00
Ettore Di Giacinto
9db9c1bf19 Add integration test for reinstall warning 2020-01-01 12:04:26 +01:00
Ettore Di Giacinto
5e8a29caf5 Detect already installed packages when calling install
We wasn't checking this previously, which was drawing weird errors on the CLI
2020-01-01 11:58:33 +01:00
Ettore Di Giacinto
255f768cc0 Enhance simple integration test 2019-12-31 15:59:46 +01:00
Ettore Di Giacinto
1af235dfdc Add integration tests to run on travis 2019-12-31 15:44:00 +01:00
Ettore Di Giacinto
62ebe1a82b Add integration tests 2019-12-31 15:22:11 +01:00
Ettore Di Giacinto
efdfe72568 Treat CompressionType none as default
To provide backward compatibility with repos that didn't declares it explictly
2019-12-31 12:29:53 +01:00
Ettore Di Giacinto
c193e4d320 Enhance output 2019-12-30 16:35:38 +01:00
Ettore Di Giacinto
3d5b723668 Add compression tests
Refers to #33
2019-12-30 16:35:35 +01:00
Ettore Di Giacinto
58eb483e32 Do not create new artifact on client
Otherwise we loose artifact metadata - as checksum and compressiontype

Refers to #33
2019-12-30 16:35:33 +01:00
Ettore Di Giacinto
4f65d46d56 Drop CompressedPath, or we don't have a way to compare checksums
Refers to #33
2019-12-30 16:35:30 +01:00
Ettore Di Giacinto
d48f510f14 Propagate Checksum and CompressionType when building ArtifactIndex
Refers to #33
2019-12-30 16:35:28 +01:00
Ettore Di Giacinto
ea27ada6c0 Do not return errors after we uncompress successfully
Refers to #33
2019-12-30 16:35:25 +01:00
Ettore Di Giacinto
f71c9937c4 Add compression to build CLI
Also handle how concurrency is set now.

Adds also an accessor to compiler to set the desired compression type

Refers to #33
2019-12-30 16:35:21 +01:00
Ettore Di Giacinto
475b63be95 Consume concurrency from compiler
Refers to #33
2019-12-30 16:35:18 +01:00
Ettore Di Giacinto
a40ecaea40 Use a separate attribute to handle the compressed artifact
Refers to #33
2019-12-30 16:35:15 +01:00
Ettore Di Giacinto
5155681513 Fixup tests
Refers to #33
2019-12-30 16:35:12 +01:00
Ettore Di Giacinto
d2d72c3fc4 Add package compression type
TODO: Handle the path substitution in a separate field
Adds GZip support and allows the compiler to switch compression type.

It also adds Concurrency as a compiler attribute (not consumed yet)

Refers to #33
2019-12-30 16:34:41 +01:00
Ettore Di Giacinto
bb98259a48 Add sanity check test
To verify that we are actually comparing with some data

Closes #28
2019-12-29 14:14:06 +01:00
Ettore Di Giacinto
fea6061f89 Add hash test to artifact_test
Refers to #28
2019-12-29 14:14:03 +01:00
Ettore Di Giacinto
cb98a49917 Create new Checksum struct for Artifact
Refers to #28
2019-12-29 14:13:51 +01:00
Ettore Di Giacinto
2693ec2f8c Consume artifact verify mechanism
Refers to #28
2019-12-29 14:00:03 +01:00
Ettore Di Giacinto
eeb6719529 Add accessors to Hash and Verify artifacts
Refers to #28
2019-12-29 13:59:58 +01:00
Ettore Di Giacinto
17982e9527 Add package to calculate and compare artifact checksums
Refers to #28
2019-12-29 13:59:47 +01:00
Ettore Di Giacinto
2fa9c754ae Move archive helpers to artifact
This allow in the future to swap and provide archive/compression methods without hijacking the code.

Refers to #33
2019-12-28 16:48:05 +01:00
Ettore Di Giacinto
8fffae31c7 Add dev version tag 2019-12-23 12:08:14 +01:00
2320 changed files with 614043 additions and 61958 deletions

23
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,23 @@
on: push
name: Build and release on push
jobs:
release:
name: Test and Release
runs-on: ubuntu-latest
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: 1.14.x
- name: Checkout code
uses: actions/checkout@v2
- name: Tests
run: sudo -E env "PATH=$PATH" make deps multiarch-build test-integration test-coverage
- name: Build
run: sudo -E env "PATH=$PATH" make multiarch-build && sudo chmod -R 777 release/
- name: Release
uses: fnkr/github-action-ghr@v1
if: startsWith(github.ref, 'refs/tags/')
env:
GHR_PATH: release/
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

21
.github/workflows/test.yml vendored Normal file
View File

@@ -0,0 +1,21 @@
on: pull_request
name: Build and Test
jobs:
test:
strategy:
matrix:
go-version: [1.14.x]
platform: [ubuntu-latest]
runs-on: ${{ matrix.platform }}
steps:
- name: Install Go
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
- name: Checkout code
uses: actions/checkout@v2
- name: setup-docker
uses: docker-practice/actions-setup-docker@0.0.1
- name: Tests
run: sudo -E env "PATH=$PATH" make deps multiarch-build test-integration test-coverage

2
.gitignore vendored
View File

@@ -1,2 +1,4 @@
*.swp
luet
tests/integration/shunit2
tests/integration/bin

View File

@@ -2,17 +2,18 @@ language: go
services:
- docker
go:
- "1.12"
- "1.14"
env:
- "GO15VENDOREXPERIMENT=1"
before_install:
- make deps
- curl -LO https://storage.googleapis.com/container-diff/latest/container-diff-linux-amd64 && chmod +x container-diff-linux-amd64 && mkdir -p $HOME/bin && export PATH=$PATH:$HOME/bin && mv container-diff-linux-amd64 $HOME/bin/container-diff
- sudo -E env "PATH=$PATH" apt-get install -y libcap2-bin
- sudo -E env "PATH=$PATH" make deps
script:
- make multiarch-build test
after_success:
- make coverage
- bash <(curl -s https://codecov.io/bash)
- git config --global user.name "Deployer" && git config --global user.email foo@bar.com
- go get github.com/tcnksm/ghr
- ghr -u mudler -r luet --replace $TRAVIS_TAG release/
- sudo -E env "PATH=$PATH" make multiarch-build test-integration test-coverage
#after_success:
# - |
# if [ -n "$TRAVIS_TAG" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then
# sudo -E env "PATH=$PATH" git config --global user.name "Deployer" && git config --global user.email foo@bar.com
# sudo -E env "PATH=$PATH" go get github.com/tcnksm/ghr
# sudo -E env "PATH=$PATH" ghr -u mudler -r luet --replace $TRAVIS_TAG release/
# fi

View File

@@ -1,6 +1,7 @@
FROM golang as builder
RUN apt-get update && apt-get install -y upx
ADD . /luet
RUN cd /luet && make build
RUN cd /luet && make build-small
FROM scratch
ENV LUET_NOLOCK=true

View File

@@ -1,10 +1,14 @@
# go tool nm ./luet | grep Commit
override LDFLAGS += -X "github.com/mudler/luet/cmd.BuildTime=$(shell date -u '+%Y-%m-%d %I:%M:%S %Z')"
override LDFLAGS += -X "github.com/mudler/luet/cmd.BuildCommit=$(shell git rev-parse HEAD)"
NAME ?= luet
PACKAGE_NAME ?= $(NAME)
PACKAGE_CONFLICT ?= $(PACKAGE_NAME)-beta
REVISION := $(shell git rev-parse --short HEAD || echo dev)
VERSION := $(shell git describe --tags || echo $(REVISION))
VERSION := $(shell echo $(VERSION) | sed -e 's/^v//g')
ITTERATION := $(shell date +%s)
BUILD_PLATFORMS ?= -osarch="linux/amd64" -osarch="linux/386" -osarch="linux/arm"
ROOT_DIR:=$(shell dirname $(realpath $(lastword $(MAKEFILE_LIST))))
@@ -19,12 +23,20 @@ fmt:
test:
GO111MODULE=off go get github.com/onsi/ginkgo/ginkgo
GO111MODULE=off go get github.com/onsi/gomega/...
ginkgo -race -r ./...
ginkgo -race -r -flakeAttempts 3 ./...
.PHONY: test-integration
test-integration:
tests/integration/run.sh
.PHONY: coverage
coverage:
go test ./... -race -coverprofile=coverage.txt -covermode=atomic
.PHONY: test-coverage
test-coverage:
scripts/ginkgo.coverage.sh --codecov
.PHONY: help
help:
# make all => deps test lint build
@@ -36,6 +48,8 @@ help:
.PHONY: clean
clean:
rm -rf release/
rm -rf tests/integration/shunit2
rm -rf tests/integration/bin
.PHONY: deps
deps:
@@ -49,16 +63,17 @@ deps:
.PHONY: build
build:
CGO_ENABLED=0 go build
CGO_ENABLED=0 go build -ldflags '$(LDFLAGS)'
.PHONY: build-small
build-small:
@$(MAKE) LDFLAGS+="-s -w" build
upx --brute -1 $(NAME)
.PHONY: image
image:
docker build --rm -t luet/base .
.PHONY: gox-build
gox-build:
CGO_ENABLED=0 gox $(BUILD_PLATFORMS) -output="release/$(NAME)-$(VERSION)-{{.OS}}-{{.Arch}}"
.PHONY: lint
lint:
golint ./... | grep -v "be unexported"
@@ -75,4 +90,4 @@ test-docker:
.PHONY: multiarch-build
multiarch-build:
gox $(BUILD_PLATFORMS) -output="release/$(NAME)-$(VERSION)-{{.OS}}-{{.Arch}}"
CGO_ENABLED=0 gox $(BUILD_PLATFORMS) -ldflags '$(LDFLAGS)' -output="release/$(NAME)-$(VERSION)-{{.OS}}-{{.Arch}}"

View File

@@ -1,21 +1,64 @@
# luet - Container-based Package manager
[![Docker Repository on Quay](https://quay.io/repository/luet/base/status "Docker Repository on Quay")](https://quay.io/repository/luet/base)
[![Go Report Card](https://goreportcard.com/badge/github.com/mudler/luet)](https://goreportcard.com/report/github.com/mudler/luet)
[![Build Status](https://travis-ci.org/mudler/luet.svg?branch=master)](https://travis-ci.org/mudler/luet)
[![GoDoc](https://godoc.org/github.com/mudler/luet?status.svg)](https://godoc.org/github.com/mudler/luet)
[![codecov](https://codecov.io/gh/mudler/luet/branch/master/graph/badge.svg)](https://codecov.io/gh/mudler/luet)
Luet is a Package Manager based off from containers - it uses Docker (and other tech) to sandbox your builds and generate packages from them. It has no dependencies and it is well suitable for "from scratch" environments.
Luet is a multi-platform Package Manager based off from containers - it uses Docker (and other tech) to sandbox your builds and generate packages from them. It has zero dependencies and it is well suitable for "from scratch" environments. It can also version entire rootfs and enables delivery of OTA-alike updates, making it a perfect fit for the Edge computing era and IoT embedded devices.
It offers a simple [specfile format](https://luet-lab.github.io/docs/docs/concepts/specfile/) in YAML notation to define both packages and rootfs. As it is based on containers, it can be used to build seed stages for Linux From Scratch installations and it can build and track updates for those systems.
It is written entirely in Golang and where used as package manager, it can run in from scratch environment, with zero dependencies.
## In a glance
- Luet can reuse Gentoo's portage tree hierarchy, and it is heavily inspired from it.
- It builds, installs, uninstalls and perform upgrades on machines
- Installer doesn't depend on anything
- Installer doesn't depend on anything ( 0 dep installer !), statically built
- Support for packages as "layers"
- It uses SAT solving techniques to solve the deptree ( Inspired by [OPIUM](https://ranjitjhala.github.io/static/opium.pdf) )
## Install
To install luet, you can grab a release on the [Release page](https://github.com/mudler/luet/releases) or compile it in your machine (requires Golang installed):
$ git clone https://github.com/mudler/luet.git
$ cd luet
$ make build
## Status
Luet is not feature-complete yet, it can build, install/uninstall/upgrade packages - but it doesn't support yet all the features you would normally expect from a Package Manager nowadays.
Check out the [Wiki](https://github.com/mudler/luet/wiki) for more informations.
# Dependency solving
Luet uses SAT and Reinforcement learning engine for dependency solving.
It encodes the package requirements into a SAT problem, using gophersat to solve the dependency tree and give a concrete model as result.
## SAT encoding
Each package and its constraints are encoded and built around [OPIUM](https://ranjitjhala.github.io/static/opium.pdf). Additionally, Luet treats
also selectors seamlessly while building the model, adding *ALO* ( *At least one* ) and *AMO* ( *At most one* ) rules to guarantee coherence within the installed system.
## Reinforcement learning
Luet also implements a small and portable qlearning agent that will try to solve conflict on your behalf
when they arises while trying to validate your queries against the system model.
To leverage it, simply pass ```--solver-type qlearning``` to the subcommands that supports it ( you can check out by invoking ```--help``` ).
## Documentation
[Documentation](https://luet-lab.github.io/docs) is available, or
run `luet --help`, any subcommand is documented as well, try e.g.: `luet build --help`.
## Authors
Luet is here thanks to our amazing [contributors](https://github.com/mudler/luet/graphs/contributors)!.
Luet was originally created by Ettore Di Giacinto, mudler@sabayon.org, mudler@gentoo.org.
## License
Luet is distributed under the terms of GPLv3, check out the LICENSE file.

36
cmd/box.go Normal file
View File

@@ -0,0 +1,36 @@
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package cmd
import (
. "github.com/mudler/luet/cmd/box"
"github.com/spf13/cobra"
)
var boxGroupCmd = &cobra.Command{
Use: "box [command] [OPTIONS]",
Short: "Manage luet boxes",
}
func init() {
RootCmd.AddCommand(boxGroupCmd)
boxGroupCmd.AddCommand(
NewBoxExecCommand(),
)
}

81
cmd/box/exec.go Normal file
View File

@@ -0,0 +1,81 @@
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package cmd_box
import (
"os"
b64 "encoding/base64"
"github.com/mudler/luet/pkg/box"
. "github.com/mudler/luet/pkg/logger"
"github.com/spf13/cobra"
)
func NewBoxExecCommand() *cobra.Command {
var ans = &cobra.Command{
Use: "exec [OPTIONS]",
Short: "Execute a binary in a box",
Args: cobra.OnlyValidArgs,
PreRun: func(cmd *cobra.Command, args []string) {
},
Run: func(cmd *cobra.Command, args []string) {
stdin, _ := cmd.Flags().GetBool("stdin")
stdout, _ := cmd.Flags().GetBool("stdout")
stderr, _ := cmd.Flags().GetBool("stderr")
rootfs, _ := cmd.Flags().GetString("rootfs")
base, _ := cmd.Flags().GetBool("decode")
entrypoint, _ := cmd.Flags().GetString("entrypoint")
envs, _ := cmd.Flags().GetStringArray("env")
mounts, _ := cmd.Flags().GetStringArray("mount")
if base {
var ss []string
for _, a := range args {
sDec, _ := b64.StdEncoding.DecodeString(a)
ss = append(ss, string(sDec))
}
//If the command to run is complex,using base64 to avoid bad input
args = ss
}
Info("Executing", args, "in", rootfs)
b := box.NewBox(entrypoint, args, mounts, envs, rootfs, stdin, stdout, stderr)
err := b.Run()
if err != nil {
Fatal(err)
}
},
}
path, err := os.Getwd()
if err != nil {
Fatal(err)
}
ans.Flags().String("rootfs", path, "Rootfs path")
ans.Flags().Bool("stdin", false, "Attach to stdin")
ans.Flags().Bool("stdout", true, "Attach to stdout")
ans.Flags().Bool("stderr", true, "Attach to stderr")
ans.Flags().Bool("decode", false, "Base64 decode")
ans.Flags().StringArrayP("env", "e", []string{}, "Environment settings")
ans.Flags().StringArrayP("mount", "m", []string{}, "List of paths to bind-mount from the host")
ans.Flags().String("entrypoint", "/bin/sh", "Entrypoint command (/bin/sh)")
return ans
}

View File

@@ -17,11 +17,11 @@ package cmd
import (
"io/ioutil"
"os"
"regexp"
"runtime"
helpers "github.com/mudler/luet/cmd/helpers"
"github.com/mudler/luet/pkg/compiler"
"github.com/mudler/luet/pkg/compiler/backend"
. "github.com/mudler/luet/pkg/config"
. "github.com/mudler/luet/pkg/logger"
pkg "github.com/mudler/luet/pkg/package"
tree "github.com/mudler/luet/pkg/tree"
@@ -35,25 +35,52 @@ var buildCmd = &cobra.Command{
Short: "build a package or a tree",
Long: `build packages or trees from luet tree definitions. Packages are in [category]/[name]-[version] form`,
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlag("clean", cmd.Flags().Lookup("clean"))
viper.BindPFlag("tree", cmd.Flags().Lookup("tree"))
viper.BindPFlag("destination", cmd.Flags().Lookup("destination"))
viper.BindPFlag("backend", cmd.Flags().Lookup("backend"))
viper.BindPFlag("concurrency", cmd.Flags().Lookup("concurrency"))
viper.BindPFlag("privileged", cmd.Flags().Lookup("privileged"))
viper.BindPFlag("database", cmd.Flags().Lookup("database"))
viper.BindPFlag("revdeps", cmd.Flags().Lookup("revdeps"))
viper.BindPFlag("all", cmd.Flags().Lookup("all"))
viper.BindPFlag("compression", cmd.Flags().Lookup("compression"))
viper.BindPFlag("nodeps", cmd.Flags().Lookup("nodeps"))
viper.BindPFlag("onlydeps", cmd.Flags().Lookup("onlydeps"))
viper.BindPFlag("image-repository", cmd.Flags().Lookup("image-repository"))
viper.BindPFlag("push", cmd.Flags().Lookup("push"))
viper.BindPFlag("pull", cmd.Flags().Lookup("pull"))
viper.BindPFlag("keep-images", cmd.Flags().Lookup("keep-images"))
LuetCfg.Viper.BindPFlag("keep-exported-images", cmd.Flags().Lookup("keep-exported-images"))
LuetCfg.Viper.BindPFlag("solver.type", cmd.Flags().Lookup("solver-type"))
LuetCfg.Viper.BindPFlag("solver.discount", cmd.Flags().Lookup("solver-discount"))
LuetCfg.Viper.BindPFlag("solver.rate", cmd.Flags().Lookup("solver-rate"))
LuetCfg.Viper.BindPFlag("solver.max_attempts", cmd.Flags().Lookup("solver-attempts"))
},
Run: func(cmd *cobra.Command, args []string) {
src := viper.GetString("tree")
clean := viper.GetBool("clean")
treePaths := viper.GetStringSlice("tree")
dst := viper.GetString("destination")
concurrency := viper.GetInt("concurrency")
concurrency := LuetCfg.GetGeneral().Concurrency
backendType := viper.GetString("backend")
privileged := viper.GetBool("privileged")
revdeps := viper.GetBool("revdeps")
all := viper.GetBool("all")
databaseType := viper.GetString("database")
compressionType := viper.GetString("compression")
imageRepository := viper.GetString("image-repository")
push := viper.GetBool("push")
pull := viper.GetBool("pull")
keepImages := viper.GetBool("keep-images")
nodeps := viper.GetBool("nodeps")
onlydeps := viper.GetBool("onlydeps")
keepExportedImages := viper.GetBool("keep-exported-images")
onlyTarget, _ := cmd.Flags().GetBool("only-target-package")
full, _ := cmd.Flags().GetBool("full")
skip, _ := cmd.Flags().GetBool("skip-if-metadata-exists")
compilerSpecs := compiler.NewLuetCompilationspecs()
var compilerBackend compiler.CompilerBackend
@@ -81,26 +108,68 @@ var buildCmd = &cobra.Command{
generalRecipe := tree.NewCompilerRecipe(db)
Info("Loading", src)
if len(treePaths) <= 0 {
Fatal("No tree path supplied!")
}
for _, src := range treePaths {
Info("Loading tree", src)
err := generalRecipe.Load(src)
if err != nil {
Fatal("Error: " + err.Error())
}
}
Info("Building in", dst)
err := generalRecipe.Load(src)
if err != nil {
Fatal("Error: " + err.Error())
}
luetCompiler := compiler.NewLuetCompiler(compilerBackend, generalRecipe.GetDatabase())
stype := LuetCfg.Viper.GetString("solver.type")
discount := LuetCfg.Viper.GetFloat64("solver.discount")
rate := LuetCfg.Viper.GetFloat64("solver.rate")
attempts := LuetCfg.Viper.GetInt("solver.max_attempts")
if !all {
LuetCfg.GetSolverOptions().Type = stype
LuetCfg.GetSolverOptions().LearnRate = float32(rate)
LuetCfg.GetSolverOptions().Discount = float32(discount)
LuetCfg.GetSolverOptions().MaxAttempts = attempts
Debug("Solver", LuetCfg.GetSolverOptions().CompactString())
opts := compiler.NewDefaultCompilerOptions()
opts.SolverOptions = *LuetCfg.GetSolverOptions()
opts.ImageRepository = imageRepository
opts.Clean = clean
opts.PullFirst = pull
opts.KeepImg = keepImages
opts.Push = push
opts.OnlyDeps = onlydeps
opts.NoDeps = nodeps
opts.KeepImageExport = keepExportedImages
opts.SkipIfMetadataExists = skip
opts.PackageTargetOnly = onlyTarget
luetCompiler := compiler.NewLuetCompiler(compilerBackend, generalRecipe.GetDatabase(), opts)
luetCompiler.SetConcurrency(concurrency)
luetCompiler.SetCompressionType(compiler.CompressionImplementation(compressionType))
if full {
specs, err := luetCompiler.FromDatabase(generalRecipe.GetDatabase(), true, dst)
if err != nil {
Fatal(err.Error())
}
for _, spec := range specs {
Info(":package: Selecting ", spec.GetPackage().GetName(), spec.GetPackage().GetVersion())
compilerSpecs.Add(spec)
}
} else if !all {
for _, a := range args {
decodepackage, err := regexp.Compile(`^([<>]?\~?=?)((([^\/]+)\/)?(?U)(\S+))(-(\d+(\.\d+)*[a-z]?(_(alpha|beta|pre|rc|p)\d*)*(-r\d+)?))?$`)
pack, err := helpers.ParsePackageStr(a)
if err != nil {
Fatal("Error: " + err.Error())
Fatal("Invalid package string ", a, ": ", err.Error())
}
packageInfo := decodepackage.FindAllStringSubmatch(a, -1)
category := packageInfo[0][4]
name := packageInfo[0][5]
version := packageInfo[0][1] + packageInfo[0][7]
spec, err := luetCompiler.FromPackage(&pkg.DefaultPackage{Name: name, Category: category, Version: version})
spec, err := luetCompiler.FromPackage(pack)
if err != nil {
Fatal("Error: " + err.Error())
}
@@ -125,10 +194,10 @@ var buildCmd = &cobra.Command{
var artifact []compiler.Artifact
var errs []error
if revdeps {
artifact, errs = luetCompiler.CompileWithReverseDeps(concurrency, privileged, compilerSpecs)
artifact, errs = luetCompiler.CompileWithReverseDeps(privileged, compilerSpecs)
} else {
artifact, errs = luetCompiler.CompileParallel(concurrency, privileged, compilerSpecs)
artifact, errs = luetCompiler.CompileParallel(privileged, compilerSpecs)
}
if len(errs) != 0 {
@@ -148,14 +217,30 @@ func init() {
if err != nil {
Fatal(err)
}
buildCmd.Flags().String("tree", path, "Source luet tree")
buildCmd.Flags().Bool("clean", true, "Build all packages without considering the packages present in the build directory")
buildCmd.Flags().StringSliceP("tree", "t", []string{}, "Path of the tree to use.")
buildCmd.Flags().String("backend", "docker", "backend used (docker,img)")
buildCmd.Flags().Int("concurrency", runtime.NumCPU(), "Concurrency")
buildCmd.Flags().Bool("privileged", false, "Privileged (Keep permissions)")
buildCmd.Flags().String("database", "memory", "database used for solving (memory,boltdb)")
buildCmd.Flags().Bool("revdeps", false, "Build with revdeps")
buildCmd.Flags().Bool("all", false, "Build all packages in the tree")
buildCmd.Flags().Bool("all", false, "Build all specfiles in the tree")
buildCmd.Flags().Bool("full", false, "Build all packages (optimized)")
buildCmd.Flags().String("destination", path, "Destination folder")
buildCmd.Flags().String("compression", "none", "Compression alg: none, gzip")
buildCmd.Flags().String("image-repository", "luet/cache", "Default base image string for generated image")
buildCmd.Flags().Bool("push", false, "Push images to a hub")
buildCmd.Flags().Bool("pull", false, "Pull images from a hub")
buildCmd.Flags().Bool("keep-images", true, "Keep built docker images in the host")
buildCmd.Flags().Bool("nodeps", false, "Build only the target packages, skipping deps (it works only if you already built the deps locally, or by using --pull) ")
buildCmd.Flags().Bool("onlydeps", false, "Build only package dependencies")
buildCmd.Flags().Bool("keep-exported-images", false, "Keep exported images used during building")
buildCmd.Flags().Bool("skip-if-metadata-exists", false, "Skip package if metadata exists")
buildCmd.Flags().Bool("only-target-package", false, "Build packages of only the required target. Otherwise builds all the necessary ones not present in the destination")
buildCmd.Flags().String("solver-type", "", "Solver strategy")
buildCmd.Flags().Float32("solver-rate", 0.7, "Solver learning rate")
buildCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate")
buildCmd.Flags().Int("solver-attempts", 9000, "Solver maximum attempts")
RootCmd.AddCommand(buildCmd)
}

70
cmd/cleanup.go Normal file
View File

@@ -0,0 +1,70 @@
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package cmd
import (
"io/ioutil"
"os"
"path/filepath"
config "github.com/mudler/luet/pkg/config"
"github.com/mudler/luet/pkg/helpers"
. "github.com/mudler/luet/pkg/logger"
"github.com/spf13/cobra"
)
var cleanupCmd = &cobra.Command{
Use: "cleanup",
Short: "Clean packages cache.",
Long: `remove downloaded packages tarballs and clean cache directory`,
Run: func(cmd *cobra.Command, args []string) {
var cleaned int = 0
// Check if cache dir exists
if helpers.Exists(config.LuetCfg.GetSystem().GetSystemPkgsCacheDirPath()) {
files, err := ioutil.ReadDir(config.LuetCfg.GetSystem().GetSystemPkgsCacheDirPath())
if err != nil {
Fatal("Error on read cachedir ", err.Error())
}
for _, file := range files {
if file.IsDir() {
continue
}
if config.LuetCfg.GetGeneral().Debug {
Info("Removing ", file.Name())
}
err := os.RemoveAll(
filepath.Join(config.LuetCfg.GetSystem().GetSystemPkgsCacheDirPath(), file.Name()))
if err != nil {
Fatal("Error on removing", file.Name())
}
cleaned++
}
}
Info("Cleaned: ", cleaned, "packages.")
},
}
func init() {
RootCmd.AddCommand(cleanupCmd)
}

78
cmd/config.go Normal file
View File

@@ -0,0 +1,78 @@
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package cmd
import (
"fmt"
config "github.com/mudler/luet/pkg/config"
installer "github.com/mudler/luet/pkg/installer"
"github.com/spf13/cobra"
)
var configCmd = &cobra.Command{
Use: "config",
Short: "Print config",
Long: `Show luet configuration`,
Aliases: []string{"c"},
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(config.LuetCfg.GetLogging())
fmt.Println(config.LuetCfg.GetGeneral())
fmt.Println(config.LuetCfg.GetSystem())
if len(config.LuetCfg.CacheRepositories) > 0 {
fmt.Println("repetitors:")
for _, r := range config.LuetCfg.CacheRepositories {
fmt.Println(" - ", r.String())
}
}
if len(config.LuetCfg.SystemRepositories) > 0 {
fmt.Println("repositories:")
for _, r := range config.LuetCfg.SystemRepositories {
fmt.Println(" - ", r.String())
}
}
if len(config.LuetCfg.RepositoriesConfDir) > 0 {
fmt.Println("repos_confdir:")
for _, dir := range config.LuetCfg.RepositoriesConfDir {
fmt.Println(" - ", dir)
}
}
if len(config.LuetCfg.ConfigProtectConfDir) > 0 {
// Load config protect configs
installer.LoadConfigProtectConfs(config.LuetCfg)
fmt.Println("config_protect_confdir:")
for _, dir := range config.LuetCfg.ConfigProtectConfDir {
fmt.Println(" - ", dir)
}
if len(config.LuetCfg.GetConfigProtectConfFiles()) > 0 {
fmt.Println("protect_conf_files:")
for _, file := range config.LuetCfg.GetConfigProtectConfFiles() {
fmt.Println(" - ", file.String())
}
}
}
},
}
func init() {
RootCmd.AddCommand(configCmd)
}

View File

@@ -16,8 +16,8 @@ package cmd
import (
"io/ioutil"
"runtime"
. "github.com/mudler/luet/pkg/config"
. "github.com/mudler/luet/pkg/logger"
pkg "github.com/mudler/luet/pkg/package"
tree "github.com/mudler/luet/pkg/tree"
@@ -33,13 +33,11 @@ var convertCmd = &cobra.Command{
Long: `Parses external PM and produces a luet parsable tree`,
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlag("type", cmd.Flags().Lookup("type"))
viper.BindPFlag("concurrency", cmd.Flags().Lookup("concurrency"))
viper.BindPFlag("database", cmd.Flags().Lookup("database"))
},
Run: func(cmd *cobra.Command, args []string) {
t := viper.GetString("type")
c := viper.GetInt("concurrency")
databaseType := viper.GetString("database")
var db pkg.PackageDatabase
@@ -54,9 +52,15 @@ var convertCmd = &cobra.Command{
var builder tree.Parser
switch t {
case "gentoo":
builder = gentoo.NewGentooBuilder(&gentoo.SimpleEbuildParser{}, c, gentoo.InMemory)
builder = gentoo.NewGentooBuilder(
&gentoo.SimpleEbuildParser{},
LuetCfg.GetGeneral().Concurrency,
gentoo.InMemory)
default: // dup
builder = gentoo.NewGentooBuilder(&gentoo.SimpleEbuildParser{}, c, gentoo.InMemory)
builder = gentoo.NewGentooBuilder(
&gentoo.SimpleEbuildParser{},
LuetCfg.GetGeneral().Concurrency,
gentoo.InMemory)
}
switch databaseType {
@@ -91,7 +95,6 @@ var convertCmd = &cobra.Command{
func init() {
convertCmd.Flags().String("type", "gentoo", "source type")
convertCmd.Flags().Int("concurrency", runtime.NumCPU(), "Concurrency")
convertCmd.Flags().String("database", "memory", "database used for solving (memory,boltdb)")
RootCmd.AddCommand(convertCmd)

View File

@@ -17,8 +17,9 @@ package cmd
import (
"os"
"github.com/mudler/luet/pkg/compiler"
. "github.com/mudler/luet/pkg/config"
installer "github.com/mudler/luet/pkg/installer"
. "github.com/mudler/luet/pkg/logger"
pkg "github.com/mudler/luet/pkg/package"
@@ -35,23 +36,89 @@ var createrepoCmd = &cobra.Command{
viper.BindPFlag("tree", cmd.Flags().Lookup("tree"))
viper.BindPFlag("output", cmd.Flags().Lookup("output"))
viper.BindPFlag("name", cmd.Flags().Lookup("name"))
viper.BindPFlag("uri", cmd.Flags().Lookup("uri"))
viper.BindPFlag("descr", cmd.Flags().Lookup("descr"))
viper.BindPFlag("urls", cmd.Flags().Lookup("urls"))
viper.BindPFlag("type", cmd.Flags().Lookup("type"))
viper.BindPFlag("tree-compression", cmd.Flags().Lookup("tree-compression"))
viper.BindPFlag("tree-filename", cmd.Flags().Lookup("tree-filename"))
viper.BindPFlag("meta-compression", cmd.Flags().Lookup("meta-compression"))
viper.BindPFlag("meta-filename", cmd.Flags().Lookup("meta-filename"))
viper.BindPFlag("reset-revision", cmd.Flags().Lookup("reset-revision"))
viper.BindPFlag("repo", cmd.Flags().Lookup("repo"))
},
Run: func(cmd *cobra.Command, args []string) {
var err error
var repo installer.Repository
tree := viper.GetString("tree")
treePaths := viper.GetStringSlice("tree")
dst := viper.GetString("output")
packages := viper.GetString("packages")
name := viper.GetString("name")
uri := viper.GetString("uri")
descr := viper.GetString("descr")
urls := viper.GetStringSlice("urls")
t := viper.GetString("type")
reset := viper.GetBool("reset-revision")
treetype := viper.GetString("tree-compression")
treeName := viper.GetString("tree-filename")
metatype := viper.GetString("meta-compression")
metaName := viper.GetString("meta-filename")
source_repo := viper.GetString("repo")
treeFile := installer.NewDefaultTreeRepositoryFile()
metaFile := installer.NewDefaultMetaRepositoryFile()
if source_repo != "" {
// Search for system repository
lrepo, err := LuetCfg.GetSystemRepository(source_repo)
if err != nil {
Fatal("Error: " + err.Error())
}
if len(treePaths) <= 0 {
treePaths = []string{lrepo.TreePath}
}
if t == "" {
t = lrepo.Type
}
repo, err = installer.GenerateRepository(lrepo.Name,
lrepo.Description, t,
lrepo.Urls,
lrepo.Priority,
packages,
treePaths,
pkg.NewInMemoryDatabase(false))
} else {
repo, err = installer.GenerateRepository(name, descr, t, urls, 1, packages,
treePaths, pkg.NewInMemoryDatabase(false))
}
repo, err := installer.GenerateRepository(name, uri, t, 1, packages, tree, pkg.NewInMemoryDatabase(false))
if err != nil {
Fatal("Error: " + err.Error())
}
err = repo.Write(dst)
if treetype != "" {
treeFile.SetCompressionType(compiler.CompressionImplementation(treetype))
}
if treeName != "" {
treeFile.SetFileName(treeName)
}
if metatype != "" {
metaFile.SetCompressionType(compiler.CompressionImplementation(metatype))
}
if metaName != "" {
metaFile.SetFileName(metaName)
}
repo.SetRepositoryFile(installer.REPOFILE_TREE_KEY, treeFile)
repo.SetRepositoryFile(installer.REPOFILE_META_KEY, metaFile)
err = repo.Write(dst, reset)
if err != nil {
Fatal("Error: " + err.Error())
}
@@ -64,11 +131,19 @@ func init() {
Fatal(err)
}
createrepoCmd.Flags().String("packages", path, "Packages folder (output from build)")
createrepoCmd.Flags().String("tree", path, "Source luet tree")
createrepoCmd.Flags().StringSliceP("tree", "t", []string{}, "Path of the source trees to use.")
createrepoCmd.Flags().String("output", path, "Destination folder")
createrepoCmd.Flags().String("name", "luet", "Repository name")
createrepoCmd.Flags().String("uri", path, "Repository uri")
createrepoCmd.Flags().String("type", "local", "Repository type (local)")
createrepoCmd.Flags().String("descr", "luet", "Repository description")
createrepoCmd.Flags().StringSlice("urls", []string{}, "Repository URLs")
createrepoCmd.Flags().String("type", "disk", "Repository type (disk)")
createrepoCmd.Flags().Bool("reset-revision", false, "Reset repository revision.")
createrepoCmd.Flags().String("repo", "", "Use repository defined in configuration.")
createrepoCmd.Flags().String("tree-compression", "gzip", "Compression alg: none, gzip")
createrepoCmd.Flags().String("tree-filename", installer.TREE_TARBALL, "Repository tree filename")
createrepoCmd.Flags().String("meta-compression", "none", "Compression alg: none, gzip")
createrepoCmd.Flags().String("meta-filename", installer.REPOSITORY_METAFILE+".tar", "Repository metadata filename")
RootCmd.AddCommand(createrepoCmd)
}

37
cmd/database.go Normal file
View File

@@ -0,0 +1,37 @@
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package cmd
import (
. "github.com/mudler/luet/cmd/database"
"github.com/spf13/cobra"
)
var databaseGroupCmd = &cobra.Command{
Use: "database [command] [OPTIONS]",
Short: "Manage system database (dangerous commands ahead!)",
}
func init() {
RootCmd.AddCommand(databaseGroupCmd)
databaseGroupCmd.AddCommand(
NewDatabaseCreateCommand(),
NewDatabaseRemoveCommand(),
)
}

81
cmd/database/create.go Normal file
View File

@@ -0,0 +1,81 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package cmd_database
import (
"io/ioutil"
"path/filepath"
"github.com/mudler/luet/pkg/compiler"
. "github.com/mudler/luet/pkg/logger"
pkg "github.com/mudler/luet/pkg/package"
. "github.com/mudler/luet/pkg/config"
"github.com/spf13/cobra"
)
func NewDatabaseCreateCommand() *cobra.Command {
var ans = &cobra.Command{
Use: "create <artifact_metadata1.yaml> <artifact_metadata1.yaml>",
Short: "Insert a package in the system DB",
Args: cobra.OnlyValidArgs,
PreRun: func(cmd *cobra.Command, args []string) {
LuetCfg.Viper.BindPFlag("system.database_path", cmd.Flags().Lookup("system-dbpath"))
LuetCfg.Viper.BindPFlag("system.rootfs", cmd.Flags().Lookup("system-target"))
},
Run: func(cmd *cobra.Command, args []string) {
var systemDB pkg.PackageDatabase
for _, a := range args {
dat, err := ioutil.ReadFile(a)
if err != nil {
Fatal("Failed reading ", a, ": ", err.Error())
}
art, err := compiler.NewPackageArtifactFromYaml(dat)
if err != nil {
Fatal("Failed reading yaml ", a, ": ", err.Error())
}
if LuetCfg.GetSystem().DatabaseEngine == "boltdb" {
systemDB = pkg.NewBoltDatabase(
filepath.Join(LuetCfg.GetSystem().GetSystemRepoDatabaseDirPath(), "luet.db"))
} else {
systemDB = pkg.NewInMemoryDatabase(true)
}
files, err := art.FileList()
if err != nil {
Fatal("Failed getting file list for ", a, ": ", err.Error())
}
if _, err := systemDB.CreatePackage(art.GetCompileSpec().GetPackage()); err != nil {
Fatal("Failed to create ", a, ": ", err.Error())
}
if err := systemDB.SetPackageFiles(&pkg.PackageFile{PackageFingerprint: art.GetCompileSpec().GetPackage().GetFingerPrint(), Files: files}); err != nil {
Fatal("Failed setting package files for ", a, ": ", err.Error())
}
Info(art.GetCompileSpec().GetPackage().HumanReadableString(), " created")
}
},
}
return ans
}

69
cmd/database/remove.go Normal file
View File

@@ -0,0 +1,69 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package cmd_database
import (
"path/filepath"
. "github.com/mudler/luet/pkg/logger"
helpers "github.com/mudler/luet/cmd/helpers"
. "github.com/mudler/luet/pkg/config"
pkg "github.com/mudler/luet/pkg/package"
"github.com/spf13/cobra"
)
func NewDatabaseRemoveCommand() *cobra.Command {
var ans = &cobra.Command{
Use: "remove [package1] [package2] ...",
Short: "Remove a package from the system DB (forcefully - you normally don't want to do that)",
Args: cobra.OnlyValidArgs,
PreRun: func(cmd *cobra.Command, args []string) {
LuetCfg.Viper.BindPFlag("system.database_path", cmd.Flags().Lookup("system-dbpath"))
LuetCfg.Viper.BindPFlag("system.rootfs", cmd.Flags().Lookup("system-target"))
},
Run: func(cmd *cobra.Command, args []string) {
var systemDB pkg.PackageDatabase
for _, a := range args {
pack, err := helpers.ParsePackageStr(a)
if err != nil {
Fatal("Invalid package string ", a, ": ", err.Error())
}
if LuetCfg.GetSystem().DatabaseEngine == "boltdb" {
systemDB = pkg.NewBoltDatabase(
filepath.Join(LuetCfg.GetSystem().GetSystemRepoDatabaseDirPath(), "luet.db"))
} else {
systemDB = pkg.NewInMemoryDatabase(true)
}
if err := systemDB.RemovePackage(pack); err != nil {
Fatal("Failed removing ", a, ": ", err.Error())
}
if err := systemDB.RemovePackageFiles(pack); err != nil {
Fatal("Failed removing files for ", a, ": ", err.Error())
}
}
},
}
return ans
}

86
cmd/exec.go Normal file
View File

@@ -0,0 +1,86 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package cmd
import (
"fmt"
"os"
b64 "encoding/base64"
"github.com/mudler/luet/pkg/box"
. "github.com/mudler/luet/pkg/logger"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
var execCmd = &cobra.Command{
Use: "exec --rootfs /path [command]",
Short: "Execute a command in the rootfs context",
Long: `Uses unshare technique and pivot root to execute a command inside a folder containing a valid rootfs`,
PreRun: func(cmd *cobra.Command, args []string) {
},
// If you change this, look at pkg/box/exec that runs this command and adapt
Run: func(cmd *cobra.Command, args []string) {
stdin, _ := cmd.Flags().GetBool("stdin")
stdout, _ := cmd.Flags().GetBool("stdout")
stderr, _ := cmd.Flags().GetBool("stderr")
rootfs, _ := cmd.Flags().GetString("rootfs")
base, _ := cmd.Flags().GetBool("decode")
entrypoint, _ := cmd.Flags().GetString("entrypoint")
envs, _ := cmd.Flags().GetStringArray("env")
mounts, _ := cmd.Flags().GetStringArray("mount")
if base {
var ss []string
for _, a := range args {
sDec, _ := b64.StdEncoding.DecodeString(a)
ss = append(ss, string(sDec))
}
//If the command to run is complex,using base64 to avoid bad input
args = ss
}
Info("Executing", args, "in", rootfs)
b := box.NewBox(entrypoint, args, mounts, envs, rootfs, stdin, stdout, stderr)
err := b.Exec()
if err != nil {
Fatal(errors.Wrap(err, fmt.Sprintf("entrypoint: %s rootfs: %s", entrypoint, rootfs)))
}
},
}
func init() {
path, err := os.Getwd()
if err != nil {
Fatal(err)
}
execCmd.Hidden = true
execCmd.Flags().String("rootfs", path, "Rootfs path")
execCmd.Flags().Bool("stdin", false, "Attach to stdin")
execCmd.Flags().Bool("stdout", false, "Attach to stdout")
execCmd.Flags().Bool("stderr", false, "Attach to stderr")
execCmd.Flags().Bool("decode", false, "Base64 decode")
execCmd.Flags().StringArrayP("env", "e", []string{}, "Environment settings")
execCmd.Flags().StringArrayP("mount", "m", []string{}, "List of paths to bind-mount from the host")
execCmd.Flags().String("entrypoint", "/bin/sh", "Entrypoint command (/bin/sh)")
RootCmd.AddCommand(execCmd)
}

78
cmd/helpers/cli.go Normal file
View File

@@ -0,0 +1,78 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package cmd_helpers
import (
"errors"
"fmt"
"regexp"
_gentoo "github.com/Sabayon/pkgs-checker/pkg/gentoo"
pkg "github.com/mudler/luet/pkg/package"
version "github.com/mudler/luet/pkg/versioner"
)
func CreateRegexArray(rgx []string) ([]*regexp.Regexp, error) {
ans := make([]*regexp.Regexp, len(rgx))
if len(rgx) > 0 {
for idx, reg := range rgx {
re := regexp.MustCompile(reg)
if re == nil {
return nil, errors.New("Invalid regex " + reg + "!")
}
ans[idx] = re
}
}
return ans, nil
}
func ParsePackageStr(p string) (*pkg.DefaultPackage, error) {
gp, err := _gentoo.ParsePackageStr(p)
if err != nil {
return nil, err
}
if gp.Version == "" {
gp.Version = "0"
gp.Condition = _gentoo.PkgCondGreaterEqual
}
pkgVersion := ""
if gp.VersionBuild != "" {
pkgVersion = fmt.Sprintf("%s%s%s+%s",
version.PkgSelectorConditionFromInt(gp.Condition.Int()).String(),
gp.Version,
gp.VersionSuffix,
gp.VersionBuild,
)
} else {
pkgVersion = fmt.Sprintf("%s%s%s",
version.PkgSelectorConditionFromInt(gp.Condition.Int()).String(),
gp.Version,
gp.VersionSuffix,
)
}
pack := &pkg.DefaultPackage{
Name: gp.Name,
Category: gp.Category,
Version: pkgVersion,
Uri: make([]string, 0),
}
return pack, nil
}

View File

@@ -17,68 +17,90 @@ package cmd
import (
"os"
"path/filepath"
"regexp"
"runtime"
installer "github.com/mudler/luet/pkg/installer"
helpers "github.com/mudler/luet/cmd/helpers"
. "github.com/mudler/luet/pkg/config"
. "github.com/mudler/luet/pkg/logger"
pkg "github.com/mudler/luet/pkg/package"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var installCmd = &cobra.Command{
Use: "install <pkg1> <pkg2> ...",
Short: "Install a package",
Use: "install <pkg1> <pkg2> ...",
Short: "Install a package",
Aliases: []string{"i"},
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlag("system-dbpath", cmd.Flags().Lookup("system-dbpath"))
viper.BindPFlag("system-target", cmd.Flags().Lookup("system-target"))
viper.BindPFlag("concurrency", cmd.Flags().Lookup("concurrency"))
LuetCfg.Viper.BindPFlag("system.database_path", cmd.Flags().Lookup("system-dbpath"))
LuetCfg.Viper.BindPFlag("system.rootfs", cmd.Flags().Lookup("system-target"))
LuetCfg.Viper.BindPFlag("solver.type", cmd.Flags().Lookup("solver-type"))
LuetCfg.Viper.BindPFlag("solver.discount", cmd.Flags().Lookup("solver-discount"))
LuetCfg.Viper.BindPFlag("solver.rate", cmd.Flags().Lookup("solver-rate"))
LuetCfg.Viper.BindPFlag("solver.max_attempts", cmd.Flags().Lookup("solver-attempts"))
LuetCfg.Viper.BindPFlag("onlydeps", cmd.Flags().Lookup("onlydeps"))
LuetCfg.Viper.BindPFlag("nodeps", cmd.Flags().Lookup("nodeps"))
LuetCfg.Viper.BindPFlag("force", cmd.Flags().Lookup("force"))
},
Long: `Install packages in parallel`,
Run: func(cmd *cobra.Command, args []string) {
c := []*installer.LuetRepository{}
err := viper.UnmarshalKey("system-repositories", &c)
if err != nil {
Fatal("Error: " + err.Error())
}
var toInstall []pkg.Package
var toInstall pkg.Packages
var systemDB pkg.PackageDatabase
for _, a := range args {
decodepackage, err := regexp.Compile(`^([<>]?\~?=?)((([^\/]+)\/)?(?U)(\S+))(-(\d+(\.\d+)*[a-z]?(_(alpha|beta|pre|rc|p)\d*)*(-r\d+)?))?$`)
pack, err := helpers.ParsePackageStr(a)
if err != nil {
Fatal("Error: " + err.Error())
Fatal("Invalid package string ", a, ": ", err.Error())
}
packageInfo := decodepackage.FindAllStringSubmatch(a, -1)
category := packageInfo[0][4]
name := packageInfo[0][5]
version := packageInfo[0][1] + packageInfo[0][7]
toInstall = append(toInstall, &pkg.DefaultPackage{Name: name, Category: category, Version: version})
toInstall = append(toInstall, pack)
}
// This shouldn't be necessary, but we need to unmarshal the repositories to a concrete struct, thus we need to port them back to the Repositories type
synced := installer.Repositories{}
for _, toSync := range c {
s, err := toSync.Sync()
if err != nil {
Fatal("Error: " + err.Error())
repos := installer.Repositories{}
for _, repo := range LuetCfg.SystemRepositories {
if !repo.Enable {
continue
}
synced = append(synced, s)
r := installer.NewSystemRepository(repo)
repos = append(repos, r)
}
inst := installer.NewLuetInstaller(viper.GetInt("concurrency"))
stype := LuetCfg.Viper.GetString("solver.type")
discount := LuetCfg.Viper.GetFloat64("solver.discount")
rate := LuetCfg.Viper.GetFloat64("solver.rate")
attempts := LuetCfg.Viper.GetInt("solver.max_attempts")
force := LuetCfg.Viper.GetBool("force")
nodeps := LuetCfg.Viper.GetBool("nodeps")
onlydeps := LuetCfg.Viper.GetBool("onlydeps")
LuetCfg.GetSolverOptions().Type = stype
LuetCfg.GetSolverOptions().LearnRate = float32(rate)
LuetCfg.GetSolverOptions().Discount = float32(discount)
LuetCfg.GetSolverOptions().MaxAttempts = attempts
inst.Repositories(synced)
Debug("Solver", LuetCfg.GetSolverOptions().CompactString())
os.MkdirAll(viper.GetString("system-dbpath"), os.ModePerm)
systemDB := pkg.NewBoltDatabase(filepath.Join(viper.GetString("system-dbpath"), "luet.db"))
system := &installer.System{Database: systemDB, Target: viper.GetString("system-target")}
err = inst.Install(toInstall, system)
// Load config protect configs
installer.LoadConfigProtectConfs(LuetCfg)
inst := installer.NewLuetInstaller(installer.LuetInstallerOptions{
Concurrency: LuetCfg.GetGeneral().Concurrency,
SolverOptions: *LuetCfg.GetSolverOptions(),
NoDeps: nodeps,
Force: force,
OnlyDeps: onlydeps,
PreserveSystemEssentialData: true,
})
inst.Repositories(repos)
if LuetCfg.GetSystem().DatabaseEngine == "boltdb" {
systemDB = pkg.NewBoltDatabase(
filepath.Join(LuetCfg.GetSystem().GetSystemRepoDatabaseDirPath(), "luet.db"))
} else {
systemDB = pkg.NewInMemoryDatabase(true)
}
system := &installer.System{Database: systemDB, Target: LuetCfg.GetSystem().Rootfs}
err := inst.Install(toInstall, system)
if err != nil {
Fatal("Error: " + err.Error())
}
@@ -92,7 +114,13 @@ func init() {
}
installCmd.Flags().String("system-dbpath", path, "System db path")
installCmd.Flags().String("system-target", path, "System rootpath")
installCmd.Flags().Int("concurrency", runtime.NumCPU(), "Concurrency")
installCmd.Flags().String("solver-type", "", "Solver strategy ( Defaults none, available: "+AvailableResolvers+" )")
installCmd.Flags().Float32("solver-rate", 0.7, "Solver learning rate")
installCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate")
installCmd.Flags().Int("solver-attempts", 9000, "Solver maximum attempts")
installCmd.Flags().Bool("nodeps", false, "Don't consider package dependencies (harmful!)")
installCmd.Flags().Bool("onlydeps", false, "Consider **only** package dependencies")
installCmd.Flags().Bool("force", false, "Skip errors and keep going (potentially harmful)")
RootCmd.AddCommand(installCmd)
}

88
cmd/reclaim.go Normal file
View File

@@ -0,0 +1,88 @@
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package cmd
import (
"os"
"path/filepath"
installer "github.com/mudler/luet/pkg/installer"
. "github.com/mudler/luet/pkg/config"
. "github.com/mudler/luet/pkg/logger"
pkg "github.com/mudler/luet/pkg/package"
"github.com/spf13/cobra"
)
var reclaimCmd = &cobra.Command{
Use: "reclaim",
Short: "Reclaim packages to Luet database from available repositories",
PreRun: func(cmd *cobra.Command, args []string) {
LuetCfg.Viper.BindPFlag("system.database_path", cmd.Flags().Lookup("system-dbpath"))
LuetCfg.Viper.BindPFlag("system.rootfs", cmd.Flags().Lookup("system-target"))
LuetCfg.Viper.BindPFlag("force", cmd.Flags().Lookup("force"))
},
Long: `Add packages to the systemdb if files belonging to packages
in available repositories exists in the target root.`,
Run: func(cmd *cobra.Command, args []string) {
var systemDB pkg.PackageDatabase
// This shouldn't be necessary, but we need to unmarshal the repositories to a concrete struct, thus we need to port them back to the Repositories type
repos := installer.Repositories{}
for _, repo := range LuetCfg.SystemRepositories {
if !repo.Enable {
continue
}
r := installer.NewSystemRepository(repo)
repos = append(repos, r)
}
force := LuetCfg.Viper.GetBool("force")
Debug("Solver", LuetCfg.GetSolverOptions().CompactString())
inst := installer.NewLuetInstaller(installer.LuetInstallerOptions{
Concurrency: LuetCfg.GetGeneral().Concurrency,
Force: force,
PreserveSystemEssentialData: true,
})
inst.Repositories(repos)
if LuetCfg.GetSystem().DatabaseEngine == "boltdb" {
systemDB = pkg.NewBoltDatabase(
filepath.Join(LuetCfg.GetSystem().GetSystemRepoDatabaseDirPath(), "luet.db"))
} else {
systemDB = pkg.NewInMemoryDatabase(true)
}
system := &installer.System{Database: systemDB, Target: LuetCfg.GetSystem().Rootfs}
err := inst.Reclaim(system)
if err != nil {
Fatal("Error: " + err.Error())
}
},
}
func init() {
path, err := os.Getwd()
if err != nil {
Fatal(err)
}
reclaimCmd.Flags().String("system-dbpath", path, "System db path")
reclaimCmd.Flags().String("system-target", path, "System rootpath")
reclaimCmd.Flags().Bool("force", false, "Skip errors and keep going (potentially harmful)")
RootCmd.AddCommand(reclaimCmd)
}

37
cmd/repo.go Normal file
View File

@@ -0,0 +1,37 @@
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package cmd
import (
. "github.com/mudler/luet/cmd/repo"
"github.com/spf13/cobra"
)
var repoGroupCmd = &cobra.Command{
Use: "repo [command] [OPTIONS]",
Short: "Manage repositories",
}
func init() {
RootCmd.AddCommand(repoGroupCmd)
repoGroupCmd.AddCommand(
NewRepoListCommand(),
NewRepoUpdateCommand(),
)
}

101
cmd/repo/list.go Normal file
View File

@@ -0,0 +1,101 @@
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package cmd_repo
import (
"fmt"
"path/filepath"
"strconv"
"time"
. "github.com/mudler/luet/pkg/config"
installer "github.com/mudler/luet/pkg/installer"
. "github.com/logrusorgru/aurora"
"github.com/spf13/cobra"
)
func NewRepoListCommand() *cobra.Command {
var ans = &cobra.Command{
Use: "list [OPTIONS]",
Short: "List of the configured repositories.",
Args: cobra.OnlyValidArgs,
PreRun: func(cmd *cobra.Command, args []string) {
},
Run: func(cmd *cobra.Command, args []string) {
var repoColor, repoText, repoRevision string
enable, _ := cmd.Flags().GetBool("enabled")
quiet, _ := cmd.Flags().GetBool("quiet")
repoType, _ := cmd.Flags().GetString("type")
for _, repo := range LuetCfg.SystemRepositories {
if enable && !repo.Enable {
continue
}
if repoType != "" && repo.Type != repoType {
continue
}
repoRevision = ""
if quiet {
fmt.Println(repo.Name)
} else {
if repo.Enable {
repoColor = Bold(BrightGreen(repo.Name)).String()
} else {
repoColor = Bold(BrightRed(repo.Name)).String()
}
if repo.Description != "" {
repoText = Yellow(repo.Description).String()
} else {
repoText = Yellow(repo.Urls[0]).String()
}
repobasedir := LuetCfg.GetSystem().GetRepoDatabaseDirPath(repo.Name)
if repo.Cached {
r := installer.NewSystemRepository(repo)
localRepo, _ := r.(*installer.LuetSystemRepository).ReadSpecFile(filepath.Join(repobasedir,
installer.REPOSITORY_SPECFILE), false)
if localRepo != nil {
tsec, _ := strconv.ParseInt(localRepo.GetLastUpdate(), 10, 64)
repoRevision = Bold(Red(localRepo.GetRevision())).String() +
" - " + Bold(Green(time.Unix(tsec, 0).String())).String()
}
}
if repoRevision != "" {
fmt.Println(
fmt.Sprintf("%s\n %s\n Revision %s", repoColor, repoText, repoRevision))
} else {
fmt.Println(
fmt.Sprintf("%s\n %s", repoColor, repoText))
}
}
}
},
}
ans.Flags().Bool("enabled", false, "Show only enable repositories.")
ans.Flags().BoolP("quiet", "q", false, "Show only name of the repositories.")
ans.Flags().StringP("type", "t", "", "Filter repositories of a specific type")
return ans
}

84
cmd/repo/update.go Normal file
View File

@@ -0,0 +1,84 @@
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package cmd_repo
import (
. "github.com/mudler/luet/pkg/config"
installer "github.com/mudler/luet/pkg/installer"
. "github.com/mudler/luet/pkg/logger"
"github.com/spf13/cobra"
)
func NewRepoUpdateCommand() *cobra.Command {
var ans = &cobra.Command{
Use: "update [repo1] [repo2] [OPTIONS]",
Short: "Update a specific cached repository or all cached repositories.",
Example: `
# Update all cached repositories:
$> luet repo update
# Update only repo1 and repo2
$> luet repo update repo1 repo2
`,
Aliases: []string{"up"},
PreRun: func(cmd *cobra.Command, args []string) {
},
Run: func(cmd *cobra.Command, args []string) {
ignore, _ := cmd.Flags().GetBool("ignore-errors")
force, _ := cmd.Flags().GetBool("force")
if len(args) > 0 {
for _, rname := range args {
repo, err := LuetCfg.GetSystemRepository(rname)
if err != nil && !ignore {
Fatal(err.Error())
} else if err != nil {
continue
}
r := installer.NewSystemRepository(*repo)
Spinner(32)
_, err = r.Sync(force)
if err != nil && !ignore {
Fatal("Error on sync repository " + rname + ": " + err.Error())
}
SpinnerStop()
}
} else {
for _, repo := range LuetCfg.SystemRepositories {
if repo.Cached && repo.Enable {
r := installer.NewSystemRepository(repo)
Spinner(32)
_, err := r.Sync(force)
if err != nil && !ignore {
Fatal("Error on sync repository " + r.GetName() + ": " + err.Error())
}
SpinnerStop()
}
}
}
},
}
ans.Flags().BoolP("ignore-errors", "i", false, "Ignore errors on sync repositories.")
ans.Flags().BoolP("force", "f", false, "Force resync.")
return ans
}

View File

@@ -16,87 +16,219 @@
package cmd
import (
"fmt"
"os"
"path"
"os/user"
"path/filepath"
"runtime"
"strings"
"github.com/marcsauter/single"
extensions "github.com/mudler/cobra-extensions"
config "github.com/mudler/luet/pkg/config"
helpers "github.com/mudler/luet/pkg/helpers"
. "github.com/mudler/luet/pkg/logger"
repo "github.com/mudler/luet/pkg/repository"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var cfgFile string
var Verbose bool
var LockedCommands = []string{"install", "uninstall", "upgrade"}
const LuetCLIVersion = "0.3"
const (
LuetCLIVersion = "0.8.7"
LuetEnvPrefix = "LUET"
)
// Build time and commit information.
//
// ⚠️ WARNING: should only be set by "-ldflags".
var (
BuildTime string
BuildCommit string
)
// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
Use: "luet",
Short: "Package manager for the XXth century!",
Long: `Package manager which uses containers to build packages`,
Version: LuetCLIVersion,
Version: fmt.Sprintf("%s-g%s %s", LuetCLIVersion, BuildCommit, BuildTime),
PersistentPreRun: func(cmd *cobra.Command, args []string) {
err := LoadConfig(config.LuetCfg)
if err != nil {
Fatal("failed to load configuration:", err.Error())
}
// Initialize tmpdir prefix. TODO: Move this with LoadConfig
// directly on sub command to ensure the creation only when it's
// needed.
err = config.LuetCfg.GetSystem().InitTmpDir()
if err != nil {
Fatal("failed on init tmp basedir:", err.Error())
}
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
// Cleanup all tmp directories used by luet
err := config.LuetCfg.GetSystem().CleanupTmpDir()
if err != nil {
Warning("failed on cleanup tmpdir:", err.Error())
}
},
SilenceErrors: true,
}
func LoadConfig(c *config.LuetConfig) error {
// If a config file is found, read it in.
c.Viper.ReadInConfig()
err := c.Viper.Unmarshal(&config.LuetCfg)
if err != nil {
return err
}
noSpinner := c.Viper.GetBool("no_spinner")
InitAurora()
if !noSpinner {
NewSpinner()
}
Debug("Using config file:", c.Viper.ConfigFileUsed())
if c.GetLogging().EnableLogFile && c.GetLogging().Path != "" {
// Init zap logger
err = ZapLogger()
if err != nil {
return err
}
}
// Load repositories
err = repo.LoadRepositories(c)
if err != nil {
return err
}
return nil
}
// Execute adds all child commands to the root command sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
// XXX: This is mostly from scratch images.
if os.Getenv("LUET_NOLOCK") != "true" {
s := single.New("luet")
if err := s.CheckLock(); err != nil && err == single.ErrAlreadyRunning {
Fatal("another instance of the app is already running, exiting")
} else if err != nil {
// Another error occurred, might be worth handling it as well
Fatal("failed to acquire exclusive app lock:", err.Error())
if len(os.Args) > 1 {
for _, lockedCmd := range LockedCommands {
if os.Args[1] == lockedCmd {
s := single.New("luet")
if err := s.CheckLock(); err != nil && err == single.ErrAlreadyRunning {
Fatal("another instance of the app is already running, exiting")
} else if err != nil {
// Another error occurred, might be worth handling it as well
Fatal("failed to acquire exclusive app lock:", err.Error())
}
defer s.TryUnlock()
break
}
}
}
defer s.TryUnlock()
}
if err := RootCmd.Execute(); err != nil {
Error(err)
fmt.Println(err)
os.Exit(-1)
}
}
func init() {
cobra.OnInitialize(initConfig)
RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.luet.yaml)")
RootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
pflags := RootCmd.PersistentFlags()
pflags.StringVar(&cfgFile, "config", "", "config file (default is $HOME/.luet.yaml)")
pflags.BoolP("debug", "d", false, "verbose output")
pflags.Bool("fatal", false, "Enables Warnings to exit")
pflags.Bool("enable-logfile", false, "Enable log to file")
pflags.Bool("no-spinner", false, "Disable spinner.")
pflags.Bool("color", config.LuetCfg.GetLogging().Color, "Enable/Disable color.")
pflags.Bool("emoji", config.LuetCfg.GetLogging().EnableEmoji, "Enable/Disable emoji.")
pflags.Bool("skip-config-protect", config.LuetCfg.ConfigProtectSkip,
"Disable config protect analysis.")
pflags.StringP("logfile", "l", config.LuetCfg.GetLogging().Path,
"Logfile path. Empty value disable log to file.")
// os/user doesn't work in from scratch environments.
// Check if i can retrieve user informations.
_, err := user.Current()
if err != nil {
Warning("failed to retrieve user identity:", err.Error())
}
pflags.Bool("same-owner", config.LuetCfg.GetGeneral().SameOwner, "Maintain same owner on uncompress.")
pflags.Int("concurrency", runtime.NumCPU(), "Concurrency")
config.LuetCfg.Viper.BindPFlag("logging.color", pflags.Lookup("color"))
config.LuetCfg.Viper.BindPFlag("logging.enable_emoji", pflags.Lookup("emoji"))
config.LuetCfg.Viper.BindPFlag("logging.enable_logfile", pflags.Lookup("enable-logfile"))
config.LuetCfg.Viper.BindPFlag("logging.path", pflags.Lookup("logfile"))
config.LuetCfg.Viper.BindPFlag("general.concurrency", pflags.Lookup("concurrency"))
config.LuetCfg.Viper.BindPFlag("general.debug", pflags.Lookup("debug"))
config.LuetCfg.Viper.BindPFlag("general.fatal_warnings", pflags.Lookup("fatal"))
config.LuetCfg.Viper.BindPFlag("general.same_owner", pflags.Lookup("same-owner"))
// Currently I maintain this only from cli.
config.LuetCfg.Viper.BindPFlag("no_spinner", pflags.Lookup("no-spinner"))
config.LuetCfg.Viper.BindPFlag("config_protect_skip", pflags.Lookup("skip-config-protect"))
// Extensions must be binary with the "luet-" prefix to be able to be shown in the help.
// we also accept extensions in the relative path where luet is being started, "extensions/"
exts := extensions.Discover("luet", "extensions")
for _, ex := range exts {
cobraCmd := ex.CobraCommand()
RootCmd.AddCommand(cobraCmd)
}
}
// initConfig reads in config file and ENV variables if set.
func initConfig() {
// Luet support these priorities on read configuration file:
// - command line option (if available)
// - $PWD/.luet.yaml
// - $HOME/.luet.yaml
// - /etc/luet/luet.yaml
//
// Note: currently a single viper instance support only one config name.
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
Error(err)
os.Exit(1)
}
viper.SetEnvPrefix(LuetEnvPrefix)
viper.SetConfigType("yaml")
viper.SetConfigName(".luet") // name of config file (without extension)
if cfgFile != "" { // enable ability to specify config file via flag
Info(">>> cfgFile: ", cfgFile)
if cfgFile != "" { // enable ability to specify config file via flag
viper.SetConfigFile(cfgFile)
configDir := path.Dir(cfgFile)
if configDir != "." && configDir != dir {
viper.AddConfigPath(configDir)
} else {
// Retrieve pwd directory
pwdDir, err := os.Getwd()
if err != nil {
Error(err)
os.Exit(1)
}
homeDir := helpers.GetHomeDir()
if helpers.Exists(filepath.Join(pwdDir, ".luet.yaml")) || (homeDir != "" && helpers.Exists(filepath.Join(homeDir, ".luet.yaml"))) {
viper.AddConfigPath(".")
if homeDir != "" {
viper.AddConfigPath(homeDir)
}
viper.SetConfigName(".luet")
} else {
viper.SetConfigName("luet")
viper.AddConfigPath("/etc/luet")
}
}
viper.AddConfigPath(dir)
viper.AddConfigPath(".")
viper.AddConfigPath("$HOME")
viper.AddConfigPath("/etc/luet")
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
Info("Using config file:", viper.ConfigFileUsed())
} else {
Warning(err)
}
// Create EnvKey Replacer for handle complex structure
replacer := strings.NewReplacer(".", "__")
viper.SetEnvKeyReplacer(replacer)
viper.SetTypeByDefaultValue(true)
}

View File

@@ -15,72 +15,211 @@
package cmd
import (
"fmt"
"os"
"path/filepath"
"regexp"
"runtime"
"github.com/ghodss/yaml"
. "github.com/mudler/luet/pkg/config"
installer "github.com/mudler/luet/pkg/installer"
. "github.com/mudler/luet/pkg/logger"
pkg "github.com/mudler/luet/pkg/package"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
type PackageResult struct {
Name string `json:"name"`
Category string `json:"category"`
Version string `json:"version"`
Repository string `json:"repository"`
Hidden bool `json:"hidden"`
}
type Results struct {
Packages []PackageResult `json:"packages"`
}
var searchCmd = &cobra.Command{
Use: "search <term>",
Short: "Search packages",
Long: `Search for installed and available packages`,
Use: "search <term>",
Short: "Search packages",
Long: `Search for installed and available packages`,
Aliases: []string{"s"},
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlag("system-dbpath", cmd.Flags().Lookup("system-dbpath"))
viper.BindPFlag("system-target", cmd.Flags().Lookup("system-target"))
viper.BindPFlag("concurrency", cmd.Flags().Lookup("concurrency"))
viper.BindPFlag("installed", cmd.Flags().Lookup("installed"))
LuetCfg.Viper.BindPFlag("system.database_path", cmd.Flags().Lookup("system-dbpath"))
LuetCfg.Viper.BindPFlag("system.rootfs", cmd.Flags().Lookup("system-target"))
LuetCfg.Viper.BindPFlag("installed", cmd.Flags().Lookup("installed"))
LuetCfg.Viper.BindPFlag("solver.type", cmd.Flags().Lookup("solver-type"))
LuetCfg.Viper.BindPFlag("solver.discount", cmd.Flags().Lookup("solver-discount"))
LuetCfg.Viper.BindPFlag("solver.rate", cmd.Flags().Lookup("solver-rate"))
LuetCfg.Viper.BindPFlag("solver.max_attempts", cmd.Flags().Lookup("solver-attempts"))
},
Run: func(cmd *cobra.Command, args []string) {
c := []*installer.LuetRepository{}
err := viper.UnmarshalKey("system-repositories", &c)
if err != nil {
Fatal("Error: " + err.Error())
}
var systemDB pkg.PackageDatabase
var results Results
if len(args) != 1 {
Fatal("Wrong number of arguments (expected 1)")
}
installed := viper.GetBool("installed")
hidden, _ := cmd.Flags().GetBool("hidden")
installed := LuetCfg.Viper.GetBool("installed")
stype := LuetCfg.Viper.GetString("solver.type")
discount := LuetCfg.Viper.GetFloat64("solver.discount")
rate := LuetCfg.Viper.GetFloat64("solver.rate")
attempts := LuetCfg.Viper.GetInt("solver.max_attempts")
searchWithLabel, _ := cmd.Flags().GetBool("by-label")
searchWithLabelMatch, _ := cmd.Flags().GetBool("by-label-regex")
revdeps, _ := cmd.Flags().GetBool("revdeps")
out, _ := cmd.Flags().GetString("output")
if out != "terminal" {
LuetCfg.GetLogging().SetLogLevel("error")
}
LuetCfg.GetSolverOptions().Type = stype
LuetCfg.GetSolverOptions().LearnRate = float32(rate)
LuetCfg.GetSolverOptions().Discount = float32(discount)
LuetCfg.GetSolverOptions().MaxAttempts = attempts
Debug("Solver", LuetCfg.GetSolverOptions().CompactString())
if !installed {
synced := installer.Repositories{}
for _, toSync := range c {
s, err := toSync.Sync()
if err != nil {
Fatal("Error: " + err.Error())
repos := installer.Repositories{}
for _, repo := range LuetCfg.SystemRepositories {
if !repo.Enable {
continue
}
synced = append(synced, s)
r := installer.NewSystemRepository(repo)
repos = append(repos, r)
}
Info("--- Search results: ---")
matches := synced.Search(args[0])
inst := installer.NewLuetInstaller(
installer.LuetInstallerOptions{
Concurrency: LuetCfg.GetGeneral().Concurrency,
SolverOptions: *LuetCfg.GetSolverOptions(),
},
)
inst.Repositories(repos)
synced, err := inst.SyncRepositories(false)
if err != nil {
Fatal("Error: " + err.Error())
}
Info("--- Search results (" + args[0] + "): ---")
matches := []installer.PackageMatch{}
if searchWithLabel {
matches = synced.SearchLabel(args[0])
} else if searchWithLabelMatch {
matches = synced.SearchLabelMatch(args[0])
} else {
matches = synced.Search(args[0])
}
for _, m := range matches {
Info(":package:", m.Package.GetCategory(), m.Package.GetName(), m.Package.GetVersion(), "repository:", m.Repo.GetName())
if !revdeps {
if !m.Package.IsHidden() || m.Package.IsHidden() && hidden {
Info(fmt.Sprintf(":file_folder:%s", m.Repo.GetName()), fmt.Sprintf(":package:%s", m.Package.HumanReadableString()))
results.Packages = append(results.Packages,
PackageResult{
Name: m.Package.GetName(),
Version: m.Package.GetVersion(),
Category: m.Package.GetCategory(),
Repository: m.Repo.GetName(),
Hidden: m.Package.IsHidden(),
})
}
} else {
visited := make(map[string]interface{})
for _, revdep := range m.Package.ExpandedRevdeps(m.Repo.GetTree().GetDatabase(), visited) {
if !revdep.IsHidden() || revdep.IsHidden() && hidden {
Info(fmt.Sprintf(":file_folder:%s", m.Repo.GetName()), fmt.Sprintf(":package:%s", revdep.HumanReadableString()))
results.Packages = append(results.Packages,
PackageResult{
Name: revdep.GetName(),
Version: revdep.GetVersion(),
Category: revdep.GetCategory(),
Repository: m.Repo.GetName(),
Hidden: revdep.IsHidden(),
})
}
}
}
}
} else {
os.MkdirAll(viper.GetString("system-dbpath"), os.ModePerm)
systemDB := pkg.NewBoltDatabase(filepath.Join(viper.GetString("system-dbpath"), "luet.db"))
system := &installer.System{Database: systemDB, Target: viper.GetString("system-target")}
var term = regexp.MustCompile(args[0])
for _, k := range system.Database.GetPackages() {
pack, err := system.Database.GetPackage(k)
if err == nil && term.MatchString(pack.GetName()) {
Info(":package:", pack.GetCategory(), pack.GetName(), pack.GetVersion())
if LuetCfg.GetSystem().DatabaseEngine == "boltdb" {
systemDB = pkg.NewBoltDatabase(
filepath.Join(LuetCfg.GetSystem().GetSystemRepoDatabaseDirPath(), "luet.db"))
} else {
systemDB = pkg.NewInMemoryDatabase(true)
}
system := &installer.System{Database: systemDB, Target: LuetCfg.GetSystem().Rootfs}
var err error
iMatches := pkg.Packages{}
if searchWithLabel {
iMatches, err = system.Database.FindPackageLabel(args[0])
} else if searchWithLabelMatch {
iMatches, err = system.Database.FindPackageLabelMatch(args[0])
} else {
iMatches, err = system.Database.FindPackageMatch(args[0])
}
if err != nil {
Fatal("Error: " + err.Error())
}
for _, pack := range iMatches {
if !revdeps {
if !pack.IsHidden() || pack.IsHidden() && hidden {
Info(fmt.Sprintf(":package:%s", pack.HumanReadableString()))
results.Packages = append(results.Packages,
PackageResult{
Name: pack.GetName(),
Version: pack.GetVersion(),
Category: pack.GetCategory(),
Repository: "system",
Hidden: pack.IsHidden(),
})
}
} else {
visited := make(map[string]interface{})
for _, revdep := range pack.ExpandedRevdeps(system.Database, visited) {
if !revdep.IsHidden() || revdep.IsHidden() && hidden {
Info(fmt.Sprintf(":package:%s", pack.HumanReadableString()))
results.Packages = append(results.Packages,
PackageResult{
Name: revdep.GetName(),
Version: revdep.GetVersion(),
Category: revdep.GetCategory(),
Repository: "system",
Hidden: revdep.IsHidden(),
})
}
}
}
}
}
y, err := yaml.Marshal(results)
if err != nil {
fmt.Printf("err: %v\n", err)
return
}
switch out {
case "yaml":
fmt.Println(string(y))
case "json":
j2, err := yaml.YAMLToJSON(y)
if err != nil {
fmt.Printf("err: %v\n", err)
return
}
fmt.Println(string(j2))
}
},
}
@@ -91,7 +230,16 @@ func init() {
}
searchCmd.Flags().String("system-dbpath", path, "System db path")
searchCmd.Flags().String("system-target", path, "System rootpath")
searchCmd.Flags().Int("concurrency", runtime.NumCPU(), "Concurrency")
searchCmd.Flags().Bool("installed", false, "Search between system packages")
searchCmd.Flags().String("solver-type", "", "Solver strategy ( Defaults none, available: "+AvailableResolvers+" )")
searchCmd.Flags().StringP("output", "o", "terminal", "Output format ( Defaults: terminal, available: json,yaml )")
searchCmd.Flags().Float32("solver-rate", 0.7, "Solver learning rate")
searchCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate")
searchCmd.Flags().Int("solver-attempts", 9000, "Solver maximum attempts")
searchCmd.Flags().Bool("by-label", false, "Search packages through label")
searchCmd.Flags().Bool("by-label-regex", false, "Search packages through label regex")
searchCmd.Flags().Bool("revdeps", false, "Search package reverse dependencies")
searchCmd.Flags().Bool("hidden", false, "Include hidden packages")
RootCmd.AddCommand(searchCmd)
}

59
cmd/serve-repo.go Normal file
View File

@@ -0,0 +1,59 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package cmd
import (
"net/http"
"os"
. "github.com/mudler/luet/pkg/logger"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var serverepoCmd = &cobra.Command{
Use: "serve-repo",
Short: "Embedded micro-http server",
Long: `Embedded mini http server for serving local repositories`,
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlag("dir", cmd.Flags().Lookup("dir"))
viper.BindPFlag("address", cmd.Flags().Lookup("address"))
viper.BindPFlag("port", cmd.Flags().Lookup("port"))
},
Run: func(cmd *cobra.Command, args []string) {
dir := viper.GetString("dir")
port := viper.GetString("port")
address := viper.GetString("address")
http.Handle("/", http.FileServer(http.Dir(dir)))
Info("Serving ", dir, " on HTTP port: ", port)
Fatal(http.ListenAndServe(address+":"+port, nil))
},
}
func init() {
path, err := os.Getwd()
if err != nil {
Fatal(err)
}
serverepoCmd.Flags().String("dir", path, "Packages folder (output from build)")
serverepoCmd.Flags().String("port", "9090", "Listening port")
serverepoCmd.Flags().String("address", "0.0.0.0", "Listening address")
RootCmd.AddCommand(serverepoCmd)
}

38
cmd/tree.go Normal file
View File

@@ -0,0 +1,38 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package cmd
import (
. "github.com/mudler/luet/cmd/tree"
"github.com/spf13/cobra"
)
var treeGroupCmd = &cobra.Command{
Use: "tree [command] [OPTIONS]",
Short: "Tree operations",
}
func init() {
RootCmd.AddCommand(treeGroupCmd)
treeGroupCmd.AddCommand(
NewTreePkglistCommand(),
NewTreeValidateCommand(),
NewTreeBumpCommand(),
)
}

88
cmd/tree/bump.go Normal file
View File

@@ -0,0 +1,88 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package cmd_tree
import (
"fmt"
. "github.com/mudler/luet/pkg/logger"
spectooling "github.com/mudler/luet/pkg/spectooling"
tree "github.com/mudler/luet/pkg/tree"
version "github.com/mudler/luet/pkg/versioner"
"github.com/spf13/cobra"
)
func NewTreeBumpCommand() *cobra.Command {
var ans = &cobra.Command{
Use: "bump [OPTIONS]",
Short: "Bump a new package build version.",
Args: cobra.OnlyValidArgs,
PreRun: func(cmd *cobra.Command, args []string) {
df, _ := cmd.Flags().GetString("definition-file")
if df == "" {
Fatal("Mandatory definition.yaml path missing.")
}
},
Run: func(cmd *cobra.Command, args []string) {
spec, _ := cmd.Flags().GetString("definition-file")
toStdout, _ := cmd.Flags().GetBool("to-stdout")
pkgVersion, _ := cmd.Flags().GetString("pkg-version")
pack, err := tree.ReadDefinitionFile(spec)
if err != nil {
Fatal(err.Error())
}
if pkgVersion != "" {
validator := &version.WrappedVersioner{}
err := validator.Validate(pkgVersion)
if err != nil {
Fatal("Invalid version string: " + err.Error())
}
pack.SetVersion(pkgVersion)
} else {
// Retrieve version build section with Gentoo parser
err = pack.BumpBuildVersion()
if err != nil {
Fatal("Error on increment build version: " + err.Error())
}
}
if toStdout {
data, err := spectooling.NewDefaultPackageSanitized(&pack).Yaml()
if err != nil {
Fatal("Error on yaml conversion: " + err.Error())
}
fmt.Println(string(data))
} else {
err = tree.WriteDefinitionFile(&pack, spec)
if err != nil {
Fatal("Error on write definition file: " + err.Error())
}
fmt.Printf("Bumped package %s/%s-%s.\n", pack.Category, pack.Name, pack.Version)
}
},
}
ans.Flags().StringP("pkg-version", "p", "", "Set a specific package version")
ans.Flags().StringP("definition-file", "f", "", "Path of the definition to bump.")
ans.Flags().BoolP("to-stdout", "o", false, "Bump package to output.")
return ans
}

284
cmd/tree/pkglist.go Normal file
View File

@@ -0,0 +1,284 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package cmd_tree
import (
"fmt"
"sort"
//. "github.com/mudler/luet/pkg/config"
"github.com/ghodss/yaml"
helpers "github.com/mudler/luet/cmd/helpers"
. "github.com/mudler/luet/pkg/config"
. "github.com/mudler/luet/pkg/logger"
pkg "github.com/mudler/luet/pkg/package"
"github.com/mudler/luet/pkg/solver"
tree "github.com/mudler/luet/pkg/tree"
"github.com/spf13/cobra"
)
type TreePackageResult struct {
Name string `json:"name"`
Category string `json:"category"`
Version string `json:"version"`
Path string `json:"path"`
}
type TreeResults struct {
Packages []TreePackageResult `json:"packages"`
}
func pkgDetail(pkg pkg.Package) string {
ans := fmt.Sprintf(`
@@ Package: %s/%s-%s
Description: %s
License: %s`,
pkg.GetCategory(), pkg.GetName(), pkg.GetVersion(),
pkg.GetDescription(), pkg.GetLicense())
for idx, u := range pkg.GetURI() {
if idx == 0 {
ans += fmt.Sprintf(" URLs: %s", u)
} else {
ans += fmt.Sprintf(" %s", u)
}
}
return ans
}
func NewTreePkglistCommand() *cobra.Command {
var excludes []string
var matches []string
var ans = &cobra.Command{
Use: "pkglist [OPTIONS]",
Short: "List of the packages found in tree.",
Args: cobra.NoArgs,
PreRun: func(cmd *cobra.Command, args []string) {
t, _ := cmd.Flags().GetStringArray("tree")
if len(t) == 0 {
Fatal("Mandatory tree param missing.")
}
revdeps, _ := cmd.Flags().GetBool("revdeps")
deps, _ := cmd.Flags().GetBool("deps")
if revdeps && deps {
Fatal("Both revdeps and deps option used. Choice only one.")
}
},
Run: func(cmd *cobra.Command, args []string) {
var results TreeResults
var depSolver solver.PackageSolver
treePath, _ := cmd.Flags().GetStringArray("tree")
verbose, _ := cmd.Flags().GetBool("verbose")
buildtime, _ := cmd.Flags().GetBool("buildtime")
full, _ := cmd.Flags().GetBool("full")
revdeps, _ := cmd.Flags().GetBool("revdeps")
deps, _ := cmd.Flags().GetBool("deps")
out, _ := cmd.Flags().GetString("output")
if out != "terminal" {
LuetCfg.GetLogging().SetLogLevel("error")
}
var reciper tree.Builder
if buildtime {
reciper = tree.NewCompilerRecipe(pkg.NewInMemoryDatabase(false))
} else {
reciper = tree.NewInstallerRecipe(pkg.NewInMemoryDatabase(false))
}
for _, t := range treePath {
err := reciper.Load(t)
if err != nil {
Fatal("Error on load tree ", err)
}
}
if deps {
emptyInstallationDb := pkg.NewInMemoryDatabase(false)
depSolver = solver.NewSolver(pkg.NewInMemoryDatabase(false),
reciper.GetDatabase(),
emptyInstallationDb)
}
regExcludes, err := helpers.CreateRegexArray(excludes)
if err != nil {
Fatal(err.Error())
}
regMatches, err := helpers.CreateRegexArray(matches)
if err != nil {
Fatal(err.Error())
}
plist := make([]string, 0)
for _, p := range reciper.GetDatabase().World() {
pkgstr := ""
addPkg := true
if full {
pkgstr = pkgDetail(p)
} else if verbose {
pkgstr = p.HumanReadableString()
} else {
pkgstr = fmt.Sprintf("%s/%s", p.GetCategory(), p.GetName())
}
if len(matches) > 0 {
matched := false
for _, rgx := range regMatches {
if rgx.MatchString(pkgstr) {
matched = true
break
}
}
if !matched {
addPkg = false
}
}
if len(excludes) > 0 && addPkg {
for _, rgx := range regExcludes {
if rgx.MatchString(pkgstr) {
addPkg = false
break
}
}
}
if addPkg {
if revdeps {
visited := make(map[string]interface{})
for _, revdep := range p.ExpandedRevdeps(reciper.GetDatabase(), visited) {
if full {
pkgstr = pkgDetail(revdep)
} else if verbose {
pkgstr = revdep.HumanReadableString()
} else {
pkgstr = fmt.Sprintf("%s/%s", revdep.GetCategory(), revdep.GetName())
}
plist = append(plist, pkgstr)
results.Packages = append(results.Packages, TreePackageResult{
Name: revdep.GetName(),
Version: revdep.GetVersion(),
Category: revdep.GetCategory(),
Path: revdep.GetPath(),
})
}
} else if deps {
Spinner(32)
solution, err := depSolver.Install(pkg.Packages{p})
if err != nil {
Fatal(err.Error())
}
ass := solution.SearchByName(p.GetPackageName())
solution, err = solution.Order(reciper.GetDatabase(), ass.Package.GetFingerPrint())
if err != nil {
Fatal(err.Error())
}
SpinnerStop()
for _, pa := range solution {
if pa.Value {
// Exclude itself
if pa.Package.GetName() == p.GetName() && pa.Package.GetCategory() == p.GetCategory() {
continue
}
if full {
pkgstr = pkgDetail(pa.Package)
} else if verbose {
pkgstr = pa.Package.HumanReadableString()
} else {
pkgstr = fmt.Sprintf("%s/%s", pa.Package.GetCategory(), pa.Package.GetName())
}
plist = append(plist, pkgstr)
results.Packages = append(results.Packages, TreePackageResult{
Name: pa.Package.GetName(),
Version: pa.Package.GetVersion(),
Category: pa.Package.GetCategory(),
Path: pa.Package.GetPath(),
})
}
}
} else {
plist = append(plist, pkgstr)
results.Packages = append(results.Packages, TreePackageResult{
Name: p.GetName(),
Version: p.GetVersion(),
Category: p.GetCategory(),
Path: p.GetPath(),
})
}
}
}
y, err := yaml.Marshal(results)
if err != nil {
fmt.Printf("err: %v\n", err)
return
}
switch out {
case "yaml":
fmt.Println(string(y))
case "json":
j2, err := yaml.YAMLToJSON(y)
if err != nil {
fmt.Printf("err: %v\n", err)
return
}
fmt.Println(string(j2))
default:
if !deps {
sort.Strings(plist)
}
for _, p := range plist {
fmt.Println(p)
}
}
},
}
ans.Flags().BoolP("buildtime", "b", false, "Build time match")
ans.Flags().StringP("output", "o", "terminal", "Output format ( Defaults: terminal, available: json,yaml )")
ans.Flags().Bool("revdeps", false, "Search package reverse dependencies")
ans.Flags().Bool("deps", false, "Search package dependencies")
ans.Flags().BoolP("verbose", "v", false, "Add package version")
ans.Flags().BoolP("full", "f", false, "Show package detail")
ans.Flags().StringArrayP("tree", "t", []string{}, "Path of the tree to use.")
ans.Flags().StringSliceVarP(&matches, "matches", "m", []string{},
"Include only matched packages from list. (Use string as regex).")
ans.Flags().StringSliceVarP(&excludes, "exclude", "e", []string{},
"Exclude matched packages from list. (Use string as regex).")
return ans
}

474
cmd/tree/validate.go Normal file
View File

@@ -0,0 +1,474 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package cmd_tree
import (
"errors"
"fmt"
"os"
"regexp"
"sort"
"strconv"
"sync"
helpers "github.com/mudler/luet/cmd/helpers"
. "github.com/mudler/luet/pkg/config"
. "github.com/mudler/luet/pkg/logger"
pkg "github.com/mudler/luet/pkg/package"
"github.com/mudler/luet/pkg/solver"
tree "github.com/mudler/luet/pkg/tree"
"github.com/spf13/cobra"
)
type ValidateOpts struct {
WithSolver bool
OnlyRuntime bool
OnlyBuildtime bool
RegExcludes []*regexp.Regexp
RegMatches []*regexp.Regexp
Excludes []string
Matches []string
// Runtime validate stuff
RuntimeCacheDeps *pkg.InMemoryDatabase
RuntimeReciper *tree.InstallerRecipe
// Buildtime validate stuff
BuildtimeCacheDeps *pkg.InMemoryDatabase
BuildtimeReciper *tree.CompilerRecipe
Mutex sync.Mutex
BrokenPkgs int
BrokenDeps int
Errors []error
}
func (o *ValidateOpts) IncrBrokenPkgs() {
o.Mutex.Lock()
defer o.Mutex.Unlock()
o.BrokenPkgs++
}
func (o *ValidateOpts) IncrBrokenDeps() {
o.Mutex.Lock()
defer o.Mutex.Unlock()
o.BrokenDeps++
}
func (o *ValidateOpts) AddError(err error) {
o.Mutex.Lock()
defer o.Mutex.Unlock()
o.Errors = append(o.Errors, err)
}
func validatePackage(p pkg.Package, checkType string, opts *ValidateOpts, reciper tree.Builder, cacheDeps *pkg.InMemoryDatabase) error {
var errstr string
var ans error
var depSolver solver.PackageSolver
if opts.WithSolver {
emptyInstallationDb := pkg.NewInMemoryDatabase(false)
depSolver = solver.NewSolver(pkg.NewInMemoryDatabase(false),
reciper.GetDatabase(),
emptyInstallationDb)
}
found, err := reciper.GetDatabase().FindPackages(
&pkg.DefaultPackage{
Name: p.GetName(),
Category: p.GetCategory(),
Version: ">=0",
},
)
if err != nil || len(found) < 1 {
if err != nil {
errstr = err.Error()
} else {
errstr = "No packages"
}
Error(fmt.Sprintf("[%9s] %s/%s-%s: Broken. No versions could be found by database %s",
checkType,
p.GetCategory(), p.GetName(), p.GetVersion(),
errstr,
))
opts.IncrBrokenDeps()
return errors.New(
fmt.Sprintf("[%9s] %s/%s-%s: Broken. No versions could be found by database %s",
checkType,
p.GetCategory(), p.GetName(), p.GetVersion(),
errstr,
))
}
// Ensure that we use the right package from right recipier for deps
pReciper, err := reciper.GetDatabase().FindPackage(
&pkg.DefaultPackage{
Name: p.GetName(),
Category: p.GetCategory(),
Version: p.GetVersion(),
},
)
if err != nil {
errstr = fmt.Sprintf("[%9s] %s/%s-%s: Error on retrieve package - %s.",
checkType,
p.GetCategory(), p.GetName(), p.GetVersion(),
err.Error(),
)
Error(errstr)
return errors.New(errstr)
}
p = pReciper
pkgstr := fmt.Sprintf("%s/%s-%s", p.GetCategory(), p.GetName(),
p.GetVersion())
validpkg := true
if len(opts.Matches) > 0 {
matched := false
for _, rgx := range opts.RegMatches {
if rgx.MatchString(pkgstr) {
matched = true
break
}
}
if !matched {
return nil
}
}
if len(opts.Excludes) > 0 {
excluded := false
for _, rgx := range opts.RegExcludes {
if rgx.MatchString(pkgstr) {
excluded = true
break
}
}
if excluded {
return nil
}
}
Info(fmt.Sprintf("[%9s] Checking package ", checkType)+
fmt.Sprintf("%s/%s-%s", p.GetCategory(), p.GetName(), p.GetVersion()),
"with", len(p.GetRequires()), "dependencies and", len(p.GetConflicts()), "conflicts.")
all := p.GetRequires()
all = append(all, p.GetConflicts()...)
for idx, r := range all {
var deps pkg.Packages
var err error
if r.IsSelector() {
deps, err = reciper.GetDatabase().FindPackages(
&pkg.DefaultPackage{
Name: r.GetName(),
Category: r.GetCategory(),
Version: r.GetVersion(),
},
)
} else {
deps = append(deps, r)
}
if err != nil || len(deps) < 1 {
if err != nil {
errstr = err.Error()
} else {
errstr = "No packages"
}
Error(fmt.Sprintf("[%9s] %s/%s-%s: Broken Dep %s/%s-%s - %s",
checkType,
p.GetCategory(), p.GetName(), p.GetVersion(),
r.GetCategory(), r.GetName(), r.GetVersion(),
errstr,
))
opts.IncrBrokenDeps()
ans = errors.New(
fmt.Sprintf("[%9s] %s/%s-%s: Broken Dep %s/%s-%s - %s",
checkType,
p.GetCategory(), p.GetName(), p.GetVersion(),
r.GetCategory(), r.GetName(), r.GetVersion(),
errstr))
validpkg = false
} else {
Debug(fmt.Sprintf("[%9s] Find packages for dep", checkType),
fmt.Sprintf("%s/%s-%s", r.GetCategory(), r.GetName(), r.GetVersion()))
if opts.WithSolver {
Info(fmt.Sprintf("[%9s] :soap: [%2d/%2d] %s/%s-%s: %s/%s-%s",
checkType,
idx+1, len(all),
p.GetCategory(), p.GetName(), p.GetVersion(),
r.GetCategory(), r.GetName(), r.GetVersion(),
))
// Check if the solver is already been done for the deep
_, err := cacheDeps.Get(r.HashFingerprint(""))
if err == nil {
Debug(fmt.Sprintf("[%9s] :direct_hit: Cache Hit for dep", checkType),
fmt.Sprintf("%s/%s-%s", r.GetCategory(), r.GetName(), r.GetVersion()))
continue
}
Spinner(32)
solution, err := depSolver.Install(pkg.Packages{r})
ass := solution.SearchByName(r.GetPackageName())
if err == nil {
_, err = solution.Order(reciper.GetDatabase(), ass.Package.GetFingerPrint())
}
SpinnerStop()
if err != nil {
Error(fmt.Sprintf("[%9s] %s/%s-%s: solver broken for dep %s/%s-%s - %s",
checkType,
p.GetCategory(), p.GetName(), p.GetVersion(),
r.GetCategory(), r.GetName(), r.GetVersion(),
err.Error(),
))
ans = errors.New(
fmt.Sprintf("[%9s] %s/%s-%s: solver broken for Dep %s/%s-%s - %s",
checkType,
p.GetCategory(), p.GetName(), p.GetVersion(),
r.GetCategory(), r.GetName(), r.GetVersion(),
err.Error()))
opts.IncrBrokenDeps()
validpkg = false
}
// Register the key
cacheDeps.Set(r.HashFingerprint(""), "1")
}
}
}
if !validpkg {
opts.IncrBrokenPkgs()
}
return ans
}
func validateWorker(i int,
wg *sync.WaitGroup,
c <-chan pkg.Package,
opts *ValidateOpts) {
defer wg.Done()
for p := range c {
if opts.OnlyBuildtime {
// Check buildtime compiler/deps
err := validatePackage(p, "buildtime", opts, opts.BuildtimeReciper, opts.BuildtimeCacheDeps)
if err != nil {
opts.AddError(err)
continue
}
} else if opts.OnlyRuntime {
// Check runtime installer/deps
err := validatePackage(p, "runtime", opts, opts.RuntimeReciper, opts.RuntimeCacheDeps)
if err != nil {
opts.AddError(err)
continue
}
} else {
// Check runtime installer/deps
err := validatePackage(p, "runtime", opts, opts.RuntimeReciper, opts.RuntimeCacheDeps)
if err != nil {
opts.AddError(err)
continue
}
// Check buildtime compiler/deps
err = validatePackage(p, "buildtime", opts, opts.BuildtimeReciper, opts.BuildtimeCacheDeps)
if err != nil {
opts.AddError(err)
}
}
}
}
func initOpts(opts *ValidateOpts, onlyRuntime, onlyBuildtime, withSolver bool, treePaths []string) {
var err error
opts.OnlyBuildtime = onlyBuildtime
opts.OnlyRuntime = onlyRuntime
opts.WithSolver = withSolver
opts.RuntimeReciper = nil
opts.BuildtimeReciper = nil
opts.BrokenPkgs = 0
opts.BrokenDeps = 0
if onlyBuildtime {
opts.BuildtimeReciper = (tree.NewCompilerRecipe(pkg.NewInMemoryDatabase(false))).(*tree.CompilerRecipe)
} else if onlyRuntime {
opts.RuntimeReciper = (tree.NewInstallerRecipe(pkg.NewInMemoryDatabase(false))).(*tree.InstallerRecipe)
} else {
opts.BuildtimeReciper = (tree.NewCompilerRecipe(pkg.NewInMemoryDatabase(false))).(*tree.CompilerRecipe)
opts.RuntimeReciper = (tree.NewInstallerRecipe(pkg.NewInMemoryDatabase(false))).(*tree.InstallerRecipe)
}
opts.RuntimeCacheDeps = pkg.NewInMemoryDatabase(false).(*pkg.InMemoryDatabase)
opts.BuildtimeCacheDeps = pkg.NewInMemoryDatabase(false).(*pkg.InMemoryDatabase)
for _, treePath := range treePaths {
Info(fmt.Sprintf("Loading :deciduous_tree: %s...", treePath))
if opts.BuildtimeReciper != nil {
err = opts.BuildtimeReciper.Load(treePath)
if err != nil {
Fatal("Error on load tree ", err)
}
}
if opts.RuntimeReciper != nil {
err = opts.RuntimeReciper.Load(treePath)
if err != nil {
Fatal("Error on load tree ", err)
}
}
}
opts.RegExcludes, err = helpers.CreateRegexArray(opts.Excludes)
if err != nil {
Fatal(err.Error())
}
opts.RegMatches, err = helpers.CreateRegexArray(opts.Matches)
if err != nil {
Fatal(err.Error())
}
}
func NewTreeValidateCommand() *cobra.Command {
var excludes []string
var matches []string
var treePaths []string
var opts ValidateOpts
var ans = &cobra.Command{
Use: "validate [OPTIONS]",
Short: "Validate a tree or a list of packages",
Args: cobra.OnlyValidArgs,
PreRun: func(cmd *cobra.Command, args []string) {
onlyRuntime, _ := cmd.Flags().GetBool("only-runtime")
onlyBuildtime, _ := cmd.Flags().GetBool("only-buildtime")
if len(treePaths) < 1 {
Fatal("Mandatory tree param missing.")
}
if onlyRuntime && onlyBuildtime {
Fatal("Both --only-runtime and --only-buildtime options are not possibile.")
}
},
Run: func(cmd *cobra.Command, args []string) {
var reciper tree.Builder
concurrency := LuetCfg.GetGeneral().Concurrency
withSolver, _ := cmd.Flags().GetBool("with-solver")
onlyRuntime, _ := cmd.Flags().GetBool("only-runtime")
onlyBuildtime, _ := cmd.Flags().GetBool("only-buildtime")
opts.Excludes = excludes
opts.Matches = matches
initOpts(&opts, onlyRuntime, onlyBuildtime, withSolver, treePaths)
// We need at least one valid reciper for get list of the packages.
if onlyBuildtime {
reciper = opts.BuildtimeReciper
} else {
reciper = opts.RuntimeReciper
}
all := make(chan pkg.Package)
var wg = new(sync.WaitGroup)
for i := 0; i < concurrency; i++ {
wg.Add(1)
go validateWorker(i, wg, all, &opts)
}
for _, p := range reciper.GetDatabase().World() {
all <- p
}
close(all)
// Wait separately and once done close the channel
go func() {
wg.Wait()
}()
stringerrs := []string{}
for _, e := range opts.Errors {
stringerrs = append(stringerrs, e.Error())
}
sort.Strings(stringerrs)
for _, e := range stringerrs {
fmt.Println(e)
}
// fmt.Println("Broken packages:", brokenPkgs, "(", brokenDeps, "deps ).")
if len(stringerrs) != 0 {
Error(fmt.Sprintf("Found %d broken packages and %d broken deps.",
opts.BrokenPkgs, opts.BrokenDeps))
Fatal("Errors: " + strconv.Itoa(len(stringerrs)))
} else {
Info("All good! :white_check_mark:")
os.Exit(0)
}
},
}
ans.Flags().Bool("only-runtime", false, "Check only runtime dependencies.")
ans.Flags().Bool("only-buildtime", false, "Check only buildtime dependencies.")
ans.Flags().BoolP("with-solver", "s", false,
"Enable check of requires also with solver.")
ans.Flags().StringSliceVarP(&treePaths, "tree", "t", []string{},
"Path of the tree to use.")
ans.Flags().StringSliceVarP(&excludes, "exclude", "e", []string{},
"Exclude matched packages from analysis. (Use string as regex).")
ans.Flags().StringSliceVarP(&matches, "matches", "m", []string{},
"Analyze only matched packages. (Use string as regex).")
return ans
}

View File

@@ -17,50 +17,79 @@ package cmd
import (
"os"
"path/filepath"
"regexp"
"runtime"
helpers "github.com/mudler/luet/cmd/helpers"
. "github.com/mudler/luet/pkg/config"
installer "github.com/mudler/luet/pkg/installer"
. "github.com/mudler/luet/pkg/logger"
pkg "github.com/mudler/luet/pkg/package"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var uninstallCmd = &cobra.Command{
Use: "uninstall <pkg>",
Short: "Uninstall a package",
Long: `Uninstall packages`,
Use: "uninstall <pkg> <pkg2> ...",
Short: "Uninstall a package or a list of packages",
Long: `Uninstall packages`,
Aliases: []string{"rm", "un"},
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlag("system-dbpath", cmd.Flags().Lookup("system-dbpath"))
viper.BindPFlag("system-target", cmd.Flags().Lookup("system-target"))
viper.BindPFlag("concurrency", cmd.Flags().Lookup("concurrency"))
LuetCfg.Viper.BindPFlag("system.database_path", cmd.Flags().Lookup("system-dbpath"))
LuetCfg.Viper.BindPFlag("system.rootfs", cmd.Flags().Lookup("system-target"))
LuetCfg.Viper.BindPFlag("solver.type", cmd.Flags().Lookup("solver-type"))
LuetCfg.Viper.BindPFlag("solver.discount", cmd.Flags().Lookup("solver-discount"))
LuetCfg.Viper.BindPFlag("solver.rate", cmd.Flags().Lookup("solver-rate"))
LuetCfg.Viper.BindPFlag("solver.max_attempts", cmd.Flags().Lookup("solver-attempts"))
LuetCfg.Viper.BindPFlag("nodeps", cmd.Flags().Lookup("nodeps"))
LuetCfg.Viper.BindPFlag("force", cmd.Flags().Lookup("force"))
},
Run: func(cmd *cobra.Command, args []string) {
if len(args) != 1 {
Fatal("Wrong number of args")
}
var systemDB pkg.PackageDatabase
a := args[0]
decodepackage, err := regexp.Compile(`^([<>]?\~?=?)((([^\/]+)\/)?(?U)(\S+))(-(\d+(\.\d+)*[a-z]?(_(alpha|beta|pre|rc|p)\d*)*(-r\d+)?))?$`)
if err != nil {
Fatal("Error: " + err.Error())
}
packageInfo := decodepackage.FindAllStringSubmatch(a, -1)
for _, a := range args {
category := packageInfo[0][4]
name := packageInfo[0][5]
version := packageInfo[0][7]
pack, err := helpers.ParsePackageStr(a)
if err != nil {
Fatal("Invalid package string ", a, ": ", err.Error())
}
inst := installer.NewLuetInstaller(viper.GetInt("concurrency"))
os.MkdirAll(viper.GetString("system-dbpath"), os.ModePerm)
systemDB := pkg.NewBoltDatabase(filepath.Join(viper.GetString("system-dbpath"), "luet.db"))
system := &installer.System{Database: systemDB, Target: viper.GetString("system-target")}
err = inst.Uninstall(&pkg.DefaultPackage{Name: name, Category: category, Version: version}, system)
if err != nil {
Fatal("Error: " + err.Error())
stype := LuetCfg.Viper.GetString("solver.type")
discount := LuetCfg.Viper.GetFloat64("solver.discount")
rate := LuetCfg.Viper.GetFloat64("solver.rate")
attempts := LuetCfg.Viper.GetInt("solver.max_attempts")
force := LuetCfg.Viper.GetBool("force")
nodeps, _ := cmd.Flags().GetBool("nodeps")
full, _ := cmd.Flags().GetBool("full")
checkconflicts, _ := cmd.Flags().GetBool("conflictscheck")
fullClean, _ := cmd.Flags().GetBool("full-clean")
LuetCfg.GetSolverOptions().Type = stype
LuetCfg.GetSolverOptions().LearnRate = float32(rate)
LuetCfg.GetSolverOptions().Discount = float32(discount)
LuetCfg.GetSolverOptions().MaxAttempts = attempts
Debug("Solver", LuetCfg.GetSolverOptions().CompactString())
inst := installer.NewLuetInstaller(installer.LuetInstallerOptions{
Concurrency: LuetCfg.GetGeneral().Concurrency,
SolverOptions: *LuetCfg.GetSolverOptions(),
NoDeps: nodeps,
Force: force,
FullUninstall: full,
FullCleanUninstall: fullClean,
CheckConflicts: checkconflicts,
})
if LuetCfg.GetSystem().DatabaseEngine == "boltdb" {
systemDB = pkg.NewBoltDatabase(
filepath.Join(LuetCfg.GetSystem().GetSystemRepoDatabaseDirPath(), "luet.db"))
} else {
systemDB = pkg.NewInMemoryDatabase(true)
}
system := &installer.System{Database: systemDB, Target: LuetCfg.GetSystem().Rootfs}
err = inst.Uninstall(pack, system)
if err != nil {
Fatal("Error: " + err.Error())
}
}
},
}
@@ -72,6 +101,15 @@ func init() {
}
uninstallCmd.Flags().String("system-dbpath", path, "System db path")
uninstallCmd.Flags().String("system-target", path, "System rootpath")
uninstallCmd.Flags().Int("concurrency", runtime.NumCPU(), "Concurrency")
uninstallCmd.Flags().String("solver-type", "", "Solver strategy ( Defaults none, available: "+AvailableResolvers+" )")
uninstallCmd.Flags().Float32("solver-rate", 0.7, "Solver learning rate")
uninstallCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate")
uninstallCmd.Flags().Int("solver-attempts", 9000, "Solver maximum attempts")
uninstallCmd.Flags().Bool("nodeps", false, "Don't consider package dependencies (harmful! overrides checkconflicts and full!)")
uninstallCmd.Flags().Bool("force", false, "Force uninstall")
uninstallCmd.Flags().Bool("full", false, "Attempts to remove as much packages as possible which aren't required (slow)")
uninstallCmd.Flags().Bool("conflictscheck", true, "Check if the package marked for deletion is required by other packages")
uninstallCmd.Flags().Bool("full-clean", false, "(experimental) Uninstall packages and all the other deps/revdeps of it.")
RootCmd.AddCommand(uninstallCmd)
}

View File

@@ -17,50 +17,83 @@ package cmd
import (
"os"
"path/filepath"
"runtime"
. "github.com/mudler/luet/pkg/config"
installer "github.com/mudler/luet/pkg/installer"
. "github.com/mudler/luet/pkg/logger"
pkg "github.com/mudler/luet/pkg/package"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var upgradeCmd = &cobra.Command{
Use: "upgrade",
Short: "Upgrades the system",
Use: "upgrade",
Short: "Upgrades the system",
Aliases: []string{"u"},
PreRun: func(cmd *cobra.Command, args []string) {
viper.BindPFlag("system-dbpath", cmd.Flags().Lookup("system-dbpath"))
viper.BindPFlag("system-target", cmd.Flags().Lookup("system-target"))
viper.BindPFlag("concurrency", cmd.Flags().Lookup("concurrency"))
LuetCfg.Viper.BindPFlag("system.database_path", installCmd.Flags().Lookup("system-dbpath"))
LuetCfg.Viper.BindPFlag("system.rootfs", installCmd.Flags().Lookup("system-target"))
LuetCfg.Viper.BindPFlag("solver.type", cmd.Flags().Lookup("solver-type"))
LuetCfg.Viper.BindPFlag("solver.discount", cmd.Flags().Lookup("solver-discount"))
LuetCfg.Viper.BindPFlag("solver.rate", cmd.Flags().Lookup("solver-rate"))
LuetCfg.Viper.BindPFlag("solver.max_attempts", cmd.Flags().Lookup("solver-attempts"))
LuetCfg.Viper.BindPFlag("force", cmd.Flags().Lookup("force"))
},
Long: `Upgrades packages in parallel`,
Run: func(cmd *cobra.Command, args []string) {
c := []*installer.LuetRepository{}
err := viper.UnmarshalKey("system-repositories", &c)
var systemDB pkg.PackageDatabase
repos := installer.Repositories{}
for _, repo := range LuetCfg.SystemRepositories {
if !repo.Enable {
continue
}
r := installer.NewSystemRepository(repo)
repos = append(repos, r)
}
stype := LuetCfg.Viper.GetString("solver.type")
discount := LuetCfg.Viper.GetFloat64("solver.discount")
rate := LuetCfg.Viper.GetFloat64("solver.rate")
attempts := LuetCfg.Viper.GetInt("solver.max_attempts")
force := LuetCfg.Viper.GetBool("force")
nodeps, _ := cmd.Flags().GetBool("nodeps")
full, _ := cmd.Flags().GetBool("full")
universe, _ := cmd.Flags().GetBool("universe")
clean, _ := cmd.Flags().GetBool("clean")
sync, _ := cmd.Flags().GetBool("sync")
LuetCfg.GetSolverOptions().Type = stype
LuetCfg.GetSolverOptions().LearnRate = float32(rate)
LuetCfg.GetSolverOptions().Discount = float32(discount)
LuetCfg.GetSolverOptions().MaxAttempts = attempts
Debug("Solver", LuetCfg.GetSolverOptions().String())
inst := installer.NewLuetInstaller(installer.LuetInstallerOptions{
Concurrency: LuetCfg.GetGeneral().Concurrency,
SolverOptions: *LuetCfg.GetSolverOptions(),
Force: force,
FullUninstall: full,
NoDeps: nodeps,
SolverUpgrade: universe,
RemoveUnavailableOnUpgrade: clean,
UpgradeNewRevisions: sync,
})
inst.Repositories(repos)
_, err := inst.SyncRepositories(false)
if err != nil {
Fatal("Error: " + err.Error())
}
// This shouldn't be necessary, but we need to unmarshal the repositories to a concrete struct, thus we need to port them back to the Repositories type
synced := installer.Repositories{}
for _, toSync := range c {
s, err := toSync.Sync()
if err != nil {
Fatal("Error: " + err.Error())
}
synced = append(synced, s)
if LuetCfg.GetSystem().DatabaseEngine == "boltdb" {
systemDB = pkg.NewBoltDatabase(
filepath.Join(LuetCfg.GetSystem().GetSystemRepoDatabaseDirPath(), "luet.db"))
} else {
systemDB = pkg.NewInMemoryDatabase(true)
}
inst := installer.NewLuetInstaller(viper.GetInt("concurrency"))
inst.Repositories(synced)
os.MkdirAll(viper.GetString("system-dbpath"), os.ModePerm)
systemDB := pkg.NewBoltDatabase(filepath.Join(viper.GetString("system-dbpath"), "luet.db"))
system := &installer.System{Database: systemDB, Target: viper.GetString("system-target")}
system := &installer.System{Database: systemDB, Target: LuetCfg.GetSystem().Rootfs}
err = inst.Upgrade(system)
if err != nil {
Fatal("Error: " + err.Error())
@@ -75,7 +108,16 @@ func init() {
}
upgradeCmd.Flags().String("system-dbpath", path, "System db path")
upgradeCmd.Flags().String("system-target", path, "System rootpath")
upgradeCmd.Flags().Int("concurrency", runtime.NumCPU(), "Concurrency")
upgradeCmd.Flags().String("solver-type", "", "Solver strategy ( Defaults none, available: "+AvailableResolvers+" )")
upgradeCmd.Flags().Float32("solver-rate", 0.7, "Solver learning rate")
upgradeCmd.Flags().Float32("solver-discount", 1.0, "Solver discount rate")
upgradeCmd.Flags().Int("solver-attempts", 9000, "Solver maximum attempts")
upgradeCmd.Flags().Bool("force", false, "Force upgrade by ignoring errors")
upgradeCmd.Flags().Bool("nodeps", false, "Don't consider package dependencies (harmful! overrides checkconflicts and full!)")
upgradeCmd.Flags().Bool("full", true, "Attempts to remove as much packages as possible which aren't required (slow)")
upgradeCmd.Flags().Bool("universe", false, "Use ONLY the SAT solver to compute upgrades (experimental)")
upgradeCmd.Flags().Bool("clean", false, "Try to drop removed packages (experimental, only when --universe is enabled)")
upgradeCmd.Flags().Bool("sync", false, "Upgrade packages with new revisions (experimental)")
RootCmd.AddCommand(upgradeCmd)
}

View File

@@ -0,0 +1,3 @@
name: "etc_conf"
dirs:
- "/etc/"

157
contrib/config/luet.yaml Normal file
View File

@@ -0,0 +1,157 @@
# Luet Configuration File
#
# ---------------------------------------------
# Logging configuration section:
# ---------------------------------------------
# logging:
# # Enable loggging to file (if path is not empty)
# enable_logfile: false
#
# Leave empty to skip logging to file.
# path: "/var/log/luet.log"
#
# Set logging level: error|warning|info|debug
# level: "info"
#
# Enable JSON log format instead of console mode.
# json_format: false.
#
# Disable/Enable color
# color: true
#
# Enable/Disable emoji
# enable_emoji: true
#
# ---------------------------------------------
# General configuration section:
# ---------------------------------------------
# general:
# Define max concurrency processes. Default is based of arch: runtime.NumCPU()
# concurrency: 1
#
# Enable Debug. If debug is active spinner is disabled.
# debug: false
#
# Show output of build execution (docker, img, etc.)
# show_build_output: false
#
# Define spinner ms
# spinner_ms: 200
#
# Define spinner charset. See https://github.com/briandowns/spinner
# spinner_charset: 22
#
# Enable warnings to exit
# fatal_warnings: false
#
# Try extracting tree/packages with the same ownership as exists in the archive (default for superuser).
# same_owner: false
#
# ---------------------------------------------
# System configuration section:
# ---------------------------------------------
# system:
#
# Rootfs path of the luet system. Default is /.
# A specific path could be used for test installation to
# a chroot environment.
# rootfs: "/"
#
# Choice database engine used for luet database.
# Supported values: boltdb|memory
# database_engine: boltdb
#
# Database path directory where store luet database.
# The path is append to rootfs option path.
# database_path: "/var/cache/luet"
#
# Define the tmpdir base directory where luet store temporary files.
# Default $TMPDIR/tmpluet
# tmpdir_base: "/tmp/tmpluet"
#
# ---------------------------------------------
# Repositories configurations directories.
# ---------------------------------------------
# Define the list of directories where luet
# try for files with .yml extension that define
# luet repository.
# repos_confdir:
# - /etc/luet/repos.conf.d
#
#
# ------------------------------------------------
# Config protect configuration files directories.
# -----------------------------------------------
# Define the list of directories where load
# configuration files with the list of config
# protect paths.
# config_protect_confdir:
# - /etc/luet/config.protect.d
#
# Permit to ignore rules defined on
# config protect confdir and packages
# annotation.
# config_protect_skip: false
#
# System repositories
# ---------------------------------------------
# In alternative to define repositories files
# through repos_confdir option is possible
# define directly the list of the repositories.
#
# repositories:
#
# Name of the repository. It's better that this name is unique. Mandatory.
# - name: "repo1"
#
# A user-friendly description of the repository
# description: "My luet repo"
#
# Type of the repository. Supported types are: dir|http. Mandatory.
# type: "dir"
#
# Define the priority of the repository on research packages. Default is 9999.
# priority: 9999
#
# Enable/Disable of the repository.
# enable: false
#
# Cached repository. If true a local cache of the remote repository tree is maintained
# locally in the $tree_path else it is used a temporary directory that is removed when
# installation of a package is completed. A cached repository reduce time on search/install
# packages. By default caching is disable.
# cached: false
#
# Path where store tree of the specifications. Default path is $database_path/repos/$repo_name/treefs
# tree_path: "/var/cache/luet/repos/local/treefs"
#
# Path where store repository metadata. Default path is $database_path/repos/$repo_name/meta
# meta_path: "/var/cache/luet/repos/local/meta"
#
# Define the list of the URL where retrieve tree and packages.
# urls:
# - https://mydomain.local/luet/repo1
#
# auth:
# Define Basic authentication header
# basic: "mybasicauth"
# Define token authentication header
# token: "mytoken"
# ---------------------------------------------
# Solver parameter configuration:
# ---------------------------------------------
# solver:
#
# Solver strategy to solve possible conflicts during depedency
# solving. Defaults to empty (none). Available: qlearning
# type: ""
#
# Solver agent learning rate. 0.1 to 1.0
# rate: 0.7
#
# Learning discount factor.
# discount: 1.0
#
# Number of overall attempts that the solver has available before bailing out.
# max_attempts: 9000
#

45
go.mod
View File

@@ -4,37 +4,46 @@ go 1.12
require (
github.com/DataDog/zstd v1.4.4 // indirect
github.com/MottainaiCI/simplestreams-builder v0.0.0-20190710131531-efb382161f56 // indirect
github.com/Sabayon/pkgs-checker v0.4.1
github.com/Sabayon/pkgs-checker v0.6.3-0.20200912135508-97c41780e9b6
github.com/asdine/storm v0.0.0-20190418133842-e0f77eada154
github.com/briandowns/spinner v1.7.0
github.com/cavaliercoder/grab v2.0.0+incompatible
github.com/crillab/gophersat v1.1.7
github.com/docker/docker v0.7.3-0.20180827131323-0c5f8d2b9b23
github.com/crillab/gophersat v1.1.9-0.20200211102949-9a8bf7f2f0a3
github.com/docker/docker v17.12.0-ce-rc1.0.20200417035958-130b0bc6032c+incompatible
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/ecooper/qlearning v0.0.0-20160612200101-3075011a69fd
github.com/ghodss/yaml v1.0.0
github.com/go-yaml/yaml v2.1.0+incompatible // indirect
github.com/hashicorp/go-version v1.2.0
github.com/jinzhu/copier v0.0.0-20180308034124-7e38e58719c3
github.com/klauspost/pgzip v1.2.1
github.com/knqyf263/go-deb-version v0.0.0-20190517075300-09fca494f03d
github.com/kr/pretty v0.2.0 // indirect
github.com/kyokomi/emoji v2.1.0+incompatible
github.com/logrusorgru/aurora v0.0.0-20190417123914-21d75270181e
github.com/marcsauter/single v0.0.0-20181104081128-f8bf46f26ec0
github.com/mattn/go-isatty v0.0.10 // indirect
github.com/mudler/docker-companion v0.4.6-0.20191110154655-b8b364100616
github.com/onsi/ginkgo v1.10.1
github.com/onsi/gomega v1.7.0
github.com/moby/sys/mount v0.1.1-0.20200320164225-6154f11e6840 // indirect
github.com/mudler/cobra-extensions v0.0.0-20200612154940-31a47105fe3d
github.com/mudler/docker-companion v0.4.6-0.20200418093252-41846f112d87
github.com/onsi/ginkgo v1.12.1
github.com/onsi/gomega v1.10.0
github.com/otiai10/copy v1.0.2
github.com/pelletier/go-toml v1.6.0 // indirect
github.com/philopon/go-toposort v0.0.0-20170620085441-9be86dbd762f
github.com/pkg/errors v0.8.1
github.com/spf13/afero v1.2.2 // indirect
github.com/spf13/cobra v0.0.5
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.5.0
github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.0.0
github.com/spf13/viper v1.6.3
github.com/stevenle/topsort v0.0.0-20130922064739-8130c1d7596b
go.etcd.io/bbolt v1.3.3
golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152 // indirect
golang.org/x/sys v0.0.0-20191110163157-d32e6e3b99c4 // indirect
gopkg.in/yaml.v2 v2.2.5
mvdan.cc/sh v2.6.4+incompatible // indirect
go.etcd.io/bbolt v1.3.4
go.uber.org/atomic v1.5.1 // indirect
go.uber.org/multierr v1.4.0 // indirect
go.uber.org/zap v1.13.0
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f // indirect
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 // indirect
gopkg.in/yaml.v2 v2.2.8
gotest.tools/v3 v3.0.2 // indirect
helm.sh/helm/v3 v3.3.4
mvdan.cc/sh/v3 v3.0.0-beta1
)
replace github.com/docker/docker => github.com/Luet-lab/moby v17.12.0-ce-rc1.0.20200605210607-749178b8f80d+incompatible

755
go.sum

File diff suppressed because it is too large Load Diff

185
pkg/box/exec.go Normal file
View File

@@ -0,0 +1,185 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package box
import (
b64 "encoding/base64"
"fmt"
"os"
"os/exec"
"strings"
"syscall"
"github.com/pkg/errors"
helpers "github.com/mudler/luet/pkg/helpers"
)
type Box interface {
Run() error
Exec() error
}
type DefaultBox struct {
Name string
Root string
Env []string
Cmd string
Args []string
HostMounts []string
Stdin, Stdout, Stderr bool
}
func NewBox(cmd string, args, hostmounts, env []string, rootfs string, stdin, stdout, stderr bool) Box {
return &DefaultBox{
Stdin: stdin,
Stdout: stdout,
Stderr: stderr,
Cmd: cmd,
Args: args,
Root: rootfs,
HostMounts: hostmounts,
Env: env,
}
}
func (b *DefaultBox) Exec() error {
if err := mountProc(b.Root); err != nil {
return errors.Wrap(err, "Failed mounting proc on rootfs")
}
if err := mountDev(b.Root); err != nil {
return errors.Wrap(err, "Failed mounting dev on rootfs")
}
for _, hostMount := range b.HostMounts {
target := hostMount
if strings.Contains(hostMount, ":") {
dest := strings.Split(hostMount, ":")
if len(dest) != 2 {
return errors.New("Invalid arguments for mount, it can be: fullpath, or source:target")
}
hostMount = dest[0]
target = dest[1]
}
if err := mountBind(hostMount, b.Root, target); err != nil {
return errors.Wrap(err, fmt.Sprintf("Failed mounting %s on rootfs", hostMount))
}
}
if err := PivotRoot(b.Root); err != nil {
return errors.Wrap(err, "Failed switching pivot on rootfs")
}
cmd := exec.Command(b.Cmd, b.Args...)
if b.Stdin {
cmd.Stdin = os.Stdin
}
if b.Stderr {
cmd.Stderr = os.Stderr
}
if b.Stdout {
cmd.Stdout = os.Stdout
}
cmd.Env = b.Env
if err := cmd.Run(); err != nil {
return errors.Wrap(err, fmt.Sprintf("Error running the %s command in box.Exec", b.Cmd))
}
return nil
}
func (b *DefaultBox) Run() error {
if !helpers.Exists(b.Root) {
return errors.New(b.Root + " does not exist")
}
// This matches with exec CLI command in luet
// TODO: Pass by env var as well
execCmd := []string{"exec", "--rootfs", b.Root, "--entrypoint", b.Cmd}
if b.Stdin {
execCmd = append(execCmd, "--stdin")
}
if b.Stderr {
execCmd = append(execCmd, "--stderr")
}
if b.Stdout {
execCmd = append(execCmd, "--stdout")
}
// Encode the command in base64 to avoid bad input from the args given
execCmd = append(execCmd, "--decode")
for _, m := range b.HostMounts {
execCmd = append(execCmd, "--mount")
execCmd = append(execCmd, m)
}
for _, e := range b.Env {
execCmd = append(execCmd, "--env")
execCmd = append(execCmd, e)
}
for _, a := range b.Args {
execCmd = append(execCmd, b64.StdEncoding.EncodeToString([]byte(a)))
}
cmd := exec.Command("/proc/self/exe", execCmd...)
if b.Stdin {
cmd.Stdin = os.Stdin
}
if b.Stderr {
cmd.Stderr = os.Stderr
}
if b.Stdout {
cmd.Stdout = os.Stdout
}
cmd.SysProcAttr = &syscall.SysProcAttr{
Cloneflags: syscall.CLONE_NEWNS |
syscall.CLONE_NEWUTS |
syscall.CLONE_NEWIPC |
syscall.CLONE_NEWPID |
syscall.CLONE_NEWNET |
syscall.CLONE_NEWUSER,
UidMappings: []syscall.SysProcIDMap{
{
ContainerID: 0,
HostID: os.Getuid(),
Size: 1,
},
},
GidMappings: []syscall.SysProcIDMap{
{
ContainerID: 0,
HostID: os.Getgid(),
Size: 1,
},
},
}
if err := cmd.Run(); err != nil {
return errors.Wrap(err, "Failed running Box command in box.Run")
}
return nil
}

94
pkg/box/rootfs.go Normal file
View File

@@ -0,0 +1,94 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package box
import (
"os"
"path/filepath"
"syscall"
)
func PivotRoot(newroot string) error {
putold := filepath.Join(newroot, "/.pivot_root")
// bind mount newroot to itself - this is a slight hack needed to satisfy the
// pivot_root requirement that newroot and putold must not be on the same
// filesystem as the current root
if err := syscall.Mount(newroot, newroot, "", syscall.MS_BIND|syscall.MS_REC, ""); err != nil {
return err
}
// create putold directory
if err := os.MkdirAll(putold, 0700); err != nil {
return err
}
// call pivot_root
if err := syscall.PivotRoot(newroot, putold); err != nil {
return err
}
// ensure current working directory is set to new root
if err := os.Chdir("/"); err != nil {
return err
}
// umount putold, which now lives at /.pivot_root
putold = "/.pivot_root"
if err := syscall.Unmount(putold, syscall.MNT_DETACH); err != nil {
return err
}
// remove putold
if err := os.RemoveAll(putold); err != nil {
return err
}
return nil
}
func mountProc(newroot string) error {
source := "proc"
target := filepath.Join(newroot, "/proc")
fstype := "proc"
flags := 0
data := ""
os.MkdirAll(target, 0755)
if err := syscall.Mount(source, target, fstype, uintptr(flags), data); err != nil {
return err
}
return nil
}
func mountBind(hostfolder, newroot, dst string) error {
source := hostfolder
target := filepath.Join(newroot, dst)
fstype := "bind"
data := ""
os.MkdirAll(target, 0755)
if err := syscall.Mount(source, target, fstype, syscall.MS_BIND|syscall.MS_REC, data); err != nil {
return err
}
return nil
}
func mountDev(newroot string) error {
return mountBind("/dev", newroot, "/dev")
}

View File

@@ -16,22 +16,38 @@
package compiler
import (
"archive/tar"
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"path/filepath"
"regexp"
system "github.com/docker/docker/pkg/system"
gzip "github.com/klauspost/pgzip"
//"strconv"
"strings"
"sync"
. "github.com/mudler/luet/pkg/logger"
"github.com/mudler/luet/pkg/solver"
yaml "gopkg.in/yaml.v2"
. "github.com/mudler/luet/pkg/config"
"github.com/mudler/luet/pkg/helpers"
. "github.com/mudler/luet/pkg/logger"
pkg "github.com/mudler/luet/pkg/package"
"github.com/mudler/luet/pkg/solver"
"github.com/pkg/errors"
yaml "gopkg.in/yaml.v2"
)
type CompressionImplementation string
const (
None CompressionImplementation = "none" // e.g. tar for standard packages
GZip CompressionImplementation = "gzip"
)
type ArtifactIndex []Artifact
@@ -40,7 +56,16 @@ func (i ArtifactIndex) CleanPath() ArtifactIndex {
newIndex := ArtifactIndex{}
for _, n := range i {
art := n.(*PackageArtifact)
newIndex = append(newIndex, &PackageArtifact{Path: path.Base(n.GetPath()), SourceAssertion: art.SourceAssertion, CompileSpec: art.CompileSpec, Dependencies: art.Dependencies})
// FIXME: This is a dup and makes difficult to add attributes to artifacts
newIndex = append(newIndex, &PackageArtifact{
Path: path.Base(n.GetPath()),
SourceAssertion: art.SourceAssertion,
CompileSpec: art.CompileSpec,
Dependencies: art.Dependencies,
CompressionType: art.CompressionType,
Checksums: art.Checksums,
Files: art.Files,
})
}
return newIndex
//Update if exists, otherwise just create
@@ -50,19 +75,22 @@ func (i ArtifactIndex) CleanPath() ArtifactIndex {
// which will consist in just of an repository.yaml which is just the repository structure with the list of package artifact.
// In this way a generic client can fetch the packages and, after unpacking the tree, performing queries to install packages.
type PackageArtifact struct {
Path string `json:"path"`
Dependencies []*PackageArtifact `json:"dependencies"`
CompileSpec *LuetCompilationSpec `json:"compilationspec"`
Path string `json:"path"`
Dependencies []*PackageArtifact `json:"dependencies"`
CompileSpec *LuetCompilationSpec `json:"compilationspec"`
Checksums Checksums `json:"checksums"`
SourceAssertion solver.PackagesAssertions `json:"-"`
CompressionType CompressionImplementation `json:"compressiontype"`
Files []string `json:"files"`
}
func NewPackageArtifact(path string) Artifact {
return &PackageArtifact{Path: path, Dependencies: []*PackageArtifact{}}
return &PackageArtifact{Path: path, Dependencies: []*PackageArtifact{}, Checksums: Checksums{}, CompressionType: None}
}
func NewPackageArtifactFromYaml(data []byte) (Artifact, error) {
p := &PackageArtifact{}
p := &PackageArtifact{Checksums: Checksums{}}
err := yaml.Unmarshal(data, &p)
if err != nil {
return p, err
@@ -71,7 +99,66 @@ func NewPackageArtifactFromYaml(data []byte) (Artifact, error) {
return p, err
}
func LoadArtifactFromYaml(spec CompilationSpec) (Artifact, error) {
metaFile := spec.GetPackage().GetFingerPrint() + ".metadata.yaml"
dat, err := ioutil.ReadFile(spec.Rel(metaFile))
if err != nil {
return nil, errors.Wrap(err, "Error reading file "+metaFile)
}
art, err := NewPackageArtifactFromYaml(dat)
if err != nil {
return nil, errors.Wrap(err, "Error writing file "+metaFile)
}
// It is relative, set it back to abs
art.SetPath(spec.Rel(art.GetPath()))
return art, nil
}
func (a *PackageArtifact) SetCompressionType(t CompressionImplementation) {
a.CompressionType = t
}
func (a *PackageArtifact) GetChecksums() Checksums {
return a.Checksums
}
func (a *PackageArtifact) SetChecksums(c Checksums) {
a.Checksums = c
}
func (a *PackageArtifact) SetFiles(f []string) {
a.Files = f
}
func (a *PackageArtifact) GetFiles() []string {
return a.Files
}
func (a *PackageArtifact) Hash() error {
return a.Checksums.Generate(a)
}
func (a *PackageArtifact) Verify() error {
sum := Checksums{}
err := sum.Generate(a)
if err != nil {
return err
}
err = sum.Compare(a.Checksums)
if err != nil {
return err
}
return nil
}
func (a *PackageArtifact) WriteYaml(dst string) error {
// First compute checksum of artifact. When we write the yaml we want to write up-to-date informations.
err := a.Hash()
if err != nil {
return errors.Wrap(err, "Failed generating checksums for artifact")
}
//p := a.CompileSpec.GetPackage().GetPath()
//a.CompileSpec.GetPackage().SetPath("")
@@ -148,11 +235,272 @@ func (a *PackageArtifact) SetPath(p string) {
a.Path = p
}
// Compress Archives and compress (TODO) to the artifact path
func (a *PackageArtifact) Compress(src string, concurrency int) error {
switch a.CompressionType {
case GZip:
err := helpers.Tar(src, a.Path)
if err != nil {
return err
}
original, err := os.Open(a.Path)
if err != nil {
return err
}
defer original.Close()
gzipfile := a.Path + ".gz"
bufferedReader := bufio.NewReader(original)
// Open a file for writing.
dst, err := os.Create(gzipfile)
if err != nil {
return err
}
// Create gzip writer.
w := gzip.NewWriter(dst)
w.SetConcurrency(concurrency, 10)
defer w.Close()
defer dst.Close()
_, err = io.Copy(w, bufferedReader)
if err != nil {
return err
}
w.Close()
os.RemoveAll(a.Path) // Remove original
// a.CompressedPath = gzipfile
a.Path = gzipfile
return nil
//a.Path = gzipfile
// Defaults to tar only (covers when "none" is supplied)
default:
return helpers.Tar(src, a.Path)
}
return errors.New("Compression type must be supplied")
}
func tarModifierWrapperFunc(dst, path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
// If the destination path already exists I rename target file name with postfix.
var destPath string
// Read data. TODO: We need change archive callback to permit to return a Reader
buffer := bytes.Buffer{}
if content != nil {
if _, err := buffer.ReadFrom(content); err != nil {
return nil, nil, err
}
}
// If file is not present on archive but is defined on mods
// I receive the callback. Prevent nil exception.
if header != nil {
switch header.Typeflag {
case tar.TypeReg:
destPath = filepath.Join(dst, path)
default:
// Nothing to do. I return original reader
return header, buffer.Bytes(), nil
}
// Check if exists
if helpers.Exists(destPath) {
for i := 1; i < 1000; i++ {
name := filepath.Join(filepath.Join(filepath.Dir(path),
fmt.Sprintf("._cfg%04d_%s", i, filepath.Base(path))))
if helpers.Exists(name) {
continue
}
Info(fmt.Sprintf("Found protected file %s. Creating %s.", destPath,
filepath.Join(dst, name)))
return &tar.Header{
Mode: header.Mode,
Typeflag: header.Typeflag,
PAXRecords: header.PAXRecords,
Name: name,
}, buffer.Bytes(), nil
}
}
}
return header, buffer.Bytes(), nil
}
func (a *PackageArtifact) GetProtectFiles() []string {
ans := []string{}
if !LuetCfg.ConfigProtectSkip &&
LuetCfg.GetConfigProtectConfFiles() != nil &&
len(LuetCfg.GetConfigProtectConfFiles()) > 0 {
for _, file := range a.Files {
for _, conf := range LuetCfg.GetConfigProtectConfFiles() {
for _, dir := range conf.Directories {
// Note file is without / at begin.
if strings.HasPrefix("/"+file, filepath.Clean(dir)) {
// docker archive modifier works with path without / at begin.
ans = append(ans, file)
goto nextFile
}
}
}
if a.CompileSpec.GetPackage().HasAnnotation(string(pkg.ConfigProtectAnnnotation)) {
dir, ok := a.CompileSpec.GetPackage().GetAnnotations()[string(pkg.ConfigProtectAnnnotation)]
if ok {
if strings.HasPrefix("/"+file, filepath.Clean(dir)) {
ans = append(ans, file)
goto nextFile
}
}
}
nextFile:
}
}
return ans
}
// Unpack Untar and decompress (TODO) to the given path
func (a *PackageArtifact) Unpack(dst string, keepPerms bool) error {
// Create
protectedFiles := a.GetProtectFiles()
tarModifier := helpers.NewTarModifierWrapper(dst, tarModifierWrapperFunc)
switch a.CompressionType {
case GZip:
// Create the uncompressed archive
archive, err := os.Create(a.GetPath() + ".uncompressed")
if err != nil {
return err
}
defer os.RemoveAll(a.GetPath() + ".uncompressed")
defer archive.Close()
original, err := os.Open(a.Path)
if err != nil {
return errors.Wrap(err, "Cannot open "+a.Path)
}
defer original.Close()
bufferedReader := bufio.NewReader(original)
r, err := gzip.NewReader(bufferedReader)
if err != nil {
return err
}
defer r.Close()
_, err = io.Copy(archive, r)
if err != nil {
return errors.Wrap(err, "Cannot copy to "+a.GetPath()+".uncompressed")
}
err = helpers.UntarProtect(a.GetPath()+".uncompressed", dst,
LuetCfg.GetGeneral().SameOwner, protectedFiles, tarModifier)
if err != nil {
return err
}
return nil
// Defaults to tar only (covers when "none" is supplied)
default:
return helpers.UntarProtect(a.GetPath(), dst, LuetCfg.GetGeneral().SameOwner,
protectedFiles, tarModifier)
}
return errors.New("Compression type must be supplied")
}
// FileList generates the list of file of a package from the local archive
func (a *PackageArtifact) FileList() ([]string, error) {
var tr *tar.Reader
switch a.CompressionType {
case GZip:
// Create the uncompressed archive
archive, err := os.Create(a.GetPath() + ".uncompressed")
if err != nil {
return []string{}, err
}
defer os.RemoveAll(a.GetPath() + ".uncompressed")
defer archive.Close()
original, err := os.Open(a.Path)
if err != nil {
return []string{}, errors.Wrap(err, "Cannot open "+a.Path)
}
defer original.Close()
bufferedReader := bufio.NewReader(original)
r, err := gzip.NewReader(bufferedReader)
if err != nil {
return []string{}, err
}
defer r.Close()
tr = tar.NewReader(r)
// Defaults to tar only (covers when "none" is supplied)
default:
tarFile, err := os.Open(a.GetPath())
if err != nil {
return []string{}, errors.Wrap(err, "Could not open package archive")
}
defer tarFile.Close()
tr = tar.NewReader(tarFile)
}
var files []string
// untar each segment
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return []string{}, err
}
// determine proper file path info
finfo := hdr.FileInfo()
fileName := hdr.Name
if finfo.Mode().IsDir() {
continue
}
files = append(files, fileName)
// if a dir, create it, then go to next segment
}
return files, nil
}
type CopyJob struct {
Src, Dst string
Artifact string
}
func copyXattr(srcPath, dstPath, attr string) error {
data, err := system.Lgetxattr(srcPath, attr)
if err != nil {
return err
}
if data != nil {
if err := system.Lsetxattr(dstPath, attr, data, 0); err != nil {
return err
}
}
return nil
}
func doCopyXattrs(srcPath, dstPath string) error {
if err := copyXattr(srcPath, dstPath, "security.capability"); err != nil {
return err
}
return copyXattr(srcPath, dstPath, "trusted.overlay.opaque")
}
func worker(i int, wg *sync.WaitGroup, s <-chan CopyJob) {
defer wg.Done()
@@ -166,25 +514,28 @@ func worker(i int, wg *sync.WaitGroup, s <-chan CopyJob) {
// continue
// }
if !helpers.Exists(job.Dst) {
_, err := os.Lstat(job.Dst)
if err != nil {
Debug("Copying ", job.Src)
if err := helpers.CopyFile(job.Src, job.Dst); err != nil {
Warning("Error copying", job, err)
}
doCopyXattrs(job.Src, job.Dst)
}
}
}
// ExtractArtifactFromDelta extracts deltas from ArtifactLayer from an image in tar format
func ExtractArtifactFromDelta(src, dst string, layers []ArtifactLayer, concurrency int, keepPerms bool, includes []string) (Artifact, error) {
func ExtractArtifactFromDelta(src, dst string, layers []ArtifactLayer, concurrency int, keepPerms bool, includes []string, t CompressionImplementation) (Artifact, error) {
archive, err := ioutil.TempDir(os.TempDir(), "archive")
archive, err := LuetCfg.GetSystem().TempDir("archive")
if err != nil {
return nil, errors.Wrap(err, "Error met while creating tempdir for archive")
}
defer os.RemoveAll(archive) // clean up
if strings.HasSuffix(src, ".tar") {
rootfs, err := ioutil.TempDir(os.TempDir(), "rootfs")
rootfs, err := LuetCfg.GetSystem().TempDir("rootfs")
if err != nil {
return nil, errors.Wrap(err, "Error met while creating tempdir for rootfs")
}
@@ -226,23 +577,73 @@ func ExtractArtifactFromDelta(src, dst string, layers []ArtifactLayer, concurren
}
}
}
for _, a := range l.Diffs.Changes {
Debug("File ", a.Name, " changed")
}
for _, a := range l.Diffs.Deletions {
Debug("File ", a.Name, " deleted")
}
}
} else {
// Otherwise just grab all
for _, l := range layers {
// Consider d.Additions (and d.Changes? - warn at least) only
for _, a := range l.Diffs.Additions {
Debug("File ", a.Name, " added")
toCopy <- CopyJob{Src: filepath.Join(src, a.Name), Dst: filepath.Join(archive, a.Name), Artifact: a.Name}
}
for _, a := range l.Diffs.Changes {
Debug("File ", a.Name, " changed")
}
for _, a := range l.Diffs.Deletions {
Debug("File ", a.Name, " deleted")
}
}
}
close(toCopy)
wg.Wait()
err = helpers.Tar(archive, dst)
a := NewPackageArtifact(dst)
a.SetCompressionType(t)
err = a.Compress(archive, concurrency)
if err != nil {
return nil, errors.Wrap(err, "Error met while creating package archive")
}
return NewPackageArtifact(dst), nil
return a, nil
}
func ComputeArtifactLayerSummary(diffs []ArtifactLayer) ArtifactLayersSummary {
ans := ArtifactLayersSummary{
Layers: make([]ArtifactLayerSummary, 0),
}
for _, layer := range diffs {
sum := ArtifactLayerSummary{
FromImage: layer.FromImage,
ToImage: layer.ToImage,
AddFiles: 0,
AddSizes: 0,
DelFiles: 0,
DelSizes: 0,
ChangeFiles: 0,
ChangeSizes: 0,
}
for _, a := range layer.Diffs.Additions {
sum.AddFiles++
sum.AddSizes += int64(a.Size)
}
for _, d := range layer.Diffs.Deletions {
sum.DelFiles++
sum.DelSizes += int64(d.Size)
}
for _, c := range layer.Diffs.Changes {
sum.ChangeFiles++
sum.ChangeSizes += int64(c.Size)
}
ans.Layers = append(ans.Layers, sum)
}
return ans
}

View File

@@ -32,7 +32,7 @@ import (
var _ = Describe("Artifact", func() {
Context("Simple package build definition", func() {
It("Generates a delta", func() {
It("Generates a verified delta", func() {
generalRecipe := tree.NewGeneralRecipe(pkg.NewInMemoryDatabase(false))
@@ -41,7 +41,7 @@ var _ = Describe("Artifact", func() {
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(1))
compiler := NewLuetCompiler(nil, generalRecipe.GetDatabase())
compiler := NewLuetCompiler(nil, generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "enman", Category: "app-admin", Version: "1.4.0"})
Expect(err).ToNot(HaveOccurred())
@@ -125,7 +125,7 @@ RUN echo bar > /test2`))
err = b.ExtractRootfs(CompilerBackendOptions{SourcePath: filepath.Join(tmpdir, "output2.tar"), Destination: rootfs}, false)
Expect(err).ToNot(HaveOccurred())
artifact, err := ExtractArtifactFromDelta(rootfs, filepath.Join(tmpdir, "package.tar"), diffs, 2, false, []string{})
artifact, err := ExtractArtifactFromDelta(rootfs, filepath.Join(tmpdir, "package.tar"), diffs, 2, false, []string{}, None)
Expect(err).ToNot(HaveOccurred())
Expect(helpers.Exists(filepath.Join(tmpdir, "package.tar"))).To(BeTrue())
err = helpers.Untar(artifact.GetPath(), unpacked, false)
@@ -138,6 +138,15 @@ RUN echo bar > /test2`))
content2, err := helpers.Read(filepath.Join(unpacked, "test2"))
Expect(err).ToNot(HaveOccurred())
Expect(content2).To(Equal("bar\n"))
err = artifact.Hash()
Expect(err).ToNot(HaveOccurred())
err = artifact.Verify()
Expect(err).ToNot(HaveOccurred())
Expect(helpers.CopyFile(filepath.Join(tmpdir, "output2.tar"), filepath.Join(tmpdir, "package.tar"))).ToNot(HaveOccurred())
err = artifact.Verify()
Expect(err).To(HaveOccurred())
})
})

View File

@@ -18,11 +18,15 @@ package backend_test
import (
"testing"
. "github.com/mudler/luet/cmd"
config "github.com/mudler/luet/pkg/config"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestSolver(t *testing.T) {
RegisterFailHandler(Fail)
LoadConfig(config.LuetCfg)
RunSpecs(t, "Backend Suite")
}

View File

@@ -0,0 +1,199 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package backend
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/mudler/luet/pkg/compiler"
"github.com/mudler/luet/pkg/config"
"github.com/pkg/errors"
)
// GenerateChanges generates changes between two images using a backend by leveraging export/extractrootfs methods
// example of json return: [
// {
// "Image1": "luet/base",
// "Image2": "alpine",
// "DiffType": "File",
// "Diff": {
// "Adds": null,
// "Dels": [
// {
// "Name": "/luetbuild",
// "Size": 5830706
// },
// {
// "Name": "/luetbuild/Dockerfile",
// "Size": 50
// },
// {
// "Name": "/luetbuild/output1",
// "Size": 5830656
// }
// ],
// "Mods": null
// }
// }
// ]
func GenerateChanges(b compiler.CompilerBackend, srcImage, dstImage string) ([]compiler.ArtifactLayer, error) {
res := compiler.ArtifactLayer{FromImage: srcImage, ToImage: dstImage}
tmpdiffs, err := config.LuetCfg.GetSystem().TempDir("extraction")
if err != nil {
return []compiler.ArtifactLayer{}, errors.Wrap(err, "Error met while creating tempdir for rootfs")
}
defer os.RemoveAll(tmpdiffs) // clean up
srcRootFS, err := ioutil.TempDir(tmpdiffs, "src")
if err != nil {
return []compiler.ArtifactLayer{}, errors.Wrap(err, "Error met while creating tempdir for rootfs")
}
defer os.RemoveAll(srcRootFS) // clean up
dstRootFS, err := ioutil.TempDir(tmpdiffs, "dst")
if err != nil {
return []compiler.ArtifactLayer{}, errors.Wrap(err, "Error met while creating tempdir for rootfs")
}
defer os.RemoveAll(dstRootFS) // clean up
// Handle both files (.tar) or images. If parameters are beginning with / , don't export the images
if !strings.HasPrefix(srcImage, "/") {
srcImageTar, err := ioutil.TempFile(tmpdiffs, "srctar")
if err != nil {
return []compiler.ArtifactLayer{}, errors.Wrap(err, "Error met while creating tempdir for rootfs")
}
defer os.Remove(srcImageTar.Name()) // clean up
srcImageExport := compiler.CompilerBackendOptions{
ImageName: srcImage,
Destination: srcImageTar.Name(),
}
err = b.ExportImage(srcImageExport)
if err != nil {
return []compiler.ArtifactLayer{}, errors.Wrap(err, "Error met while exporting src image "+srcImage)
}
srcImage = srcImageTar.Name()
}
srcImageExtract := compiler.CompilerBackendOptions{
SourcePath: srcImage,
Destination: srcRootFS,
}
err = b.ExtractRootfs(srcImageExtract, false) // No need to keep permissions as we just collect file diffs
if err != nil {
return []compiler.ArtifactLayer{}, errors.Wrap(err, "Error met while unpacking src image "+srcImage)
}
// Handle both files (.tar) or images. If parameters are beginning with / , don't export the images
if !strings.HasPrefix(dstImage, "/") {
dstImageTar, err := ioutil.TempFile(tmpdiffs, "dsttar")
if err != nil {
return []compiler.ArtifactLayer{}, errors.Wrap(err, "Error met while creating tempdir for rootfs")
}
defer os.Remove(dstImageTar.Name()) // clean up
dstImageExport := compiler.CompilerBackendOptions{
ImageName: dstImage,
Destination: dstImageTar.Name(),
}
err = b.ExportImage(dstImageExport)
if err != nil {
return []compiler.ArtifactLayer{}, errors.Wrap(err, "Error met while exporting dst image "+dstImage)
}
dstImage = dstImageTar.Name()
}
dstImageExtract := compiler.CompilerBackendOptions{
SourcePath: dstImage,
Destination: dstRootFS,
}
err = b.ExtractRootfs(dstImageExtract, false)
if err != nil {
return []compiler.ArtifactLayer{}, errors.Wrap(err, "Error met while unpacking dst image "+dstImage)
}
// Get Additions/Changes. dst -> src
err = filepath.Walk(dstRootFS, func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
}
realpath := strings.Replace(path, dstRootFS, "", -1)
fileInfo, err := os.Lstat(filepath.Join(srcRootFS, realpath))
if err == nil {
var sizeA, sizeB int64
sizeA = fileInfo.Size()
if s, err := os.Lstat(filepath.Join(dstRootFS, realpath)); err == nil {
sizeB = s.Size()
}
if sizeA != sizeB {
// fmt.Println("File changed", path, filepath.Join(srcRootFS, realpath))
res.Diffs.Changes = append(res.Diffs.Changes, compiler.ArtifactNode{
Name: filepath.Join("/", realpath),
Size: int(sizeB),
})
} else {
// fmt.Println("File already exists", path, filepath.Join(srcRootFS, realpath))
}
} else {
var sizeB int64
if s, err := os.Lstat(filepath.Join(dstRootFS, realpath)); err == nil {
sizeB = s.Size()
}
res.Diffs.Additions = append(res.Diffs.Additions, compiler.ArtifactNode{
Name: filepath.Join("/", realpath),
Size: int(sizeB),
})
// fmt.Println("File created", path, filepath.Join(srcRootFS, realpath))
}
return nil
})
if err != nil {
return []compiler.ArtifactLayer{}, errors.Wrap(err, "Error met while walking image destination")
}
// Get deletions. src -> dst
err = filepath.Walk(srcRootFS, func(path string, info os.FileInfo, err error) error {
if info.IsDir() {
return nil
}
realpath := strings.Replace(path, srcRootFS, "", -1)
if _, err = os.Lstat(filepath.Join(dstRootFS, realpath)); err != nil {
// fmt.Println("File deleted", path, filepath.Join(srcRootFS, realpath))
res.Diffs.Deletions = append(res.Diffs.Deletions, compiler.ArtifactNode{
Name: filepath.Join("/", realpath),
})
}
return nil
})
if err != nil {
return []compiler.ArtifactLayer{}, errors.Wrap(err, "Error met while walking image source")
}
return []compiler.ArtifactLayer{res}, nil
}

View File

@@ -0,0 +1,73 @@
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package backend_test
import (
"github.com/mudler/luet/pkg/compiler"
. "github.com/mudler/luet/pkg/compiler/backend"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Docker image diffs", func() {
var b compiler.CompilerBackend
BeforeEach(func() {
b = NewSimpleDockerBackend()
})
Context("Generate diffs from docker images", func() {
It("Detect no changes", func() {
err := b.DownloadImage(compiler.CompilerBackendOptions{
ImageName: "alpine:latest",
})
Expect(err).ToNot(HaveOccurred())
layers, err := GenerateChanges(b, "alpine:latest", "alpine:latest")
Expect(err).ToNot(HaveOccurred())
Expect(len(layers)).To(Equal(1))
Expect(len(layers[0].Diffs.Additions)).To(Equal(0))
Expect(len(layers[0].Diffs.Changes)).To(Equal(0))
Expect(len(layers[0].Diffs.Deletions)).To(Equal(0))
})
It("Detects additions and changed files", func() {
err := b.DownloadImage(compiler.CompilerBackendOptions{
ImageName: "quay.io/mocaccino/micro",
})
Expect(err).ToNot(HaveOccurred())
err = b.DownloadImage(compiler.CompilerBackendOptions{
ImageName: "quay.io/mocaccino/extra",
})
Expect(err).ToNot(HaveOccurred())
layers, err := GenerateChanges(b, "quay.io/mocaccino/micro", "quay.io/mocaccino/extra")
Expect(err).ToNot(HaveOccurred())
Expect(len(layers)).To(Equal(1))
Expect(len(layers[0].Diffs.Changes) > 0).To(BeTrue())
Expect(len(layers[0].Diffs.Changes[0].Name) > 0).To(BeTrue())
Expect(layers[0].Diffs.Changes[0].Size > 0).To(BeTrue())
Expect(len(layers[0].Diffs.Additions) > 0).To(BeTrue())
Expect(len(layers[0].Diffs.Additions[0].Name) > 0).To(BeTrue())
Expect(layers[0].Diffs.Additions[0].Size > 0).To(BeTrue())
Expect(len(layers[0].Diffs.Deletions)).To(Equal(0))
})
})
})

View File

@@ -17,6 +17,7 @@ package backend
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"os/exec"
@@ -26,6 +27,7 @@ import (
capi "github.com/mudler/docker-companion/api"
"github.com/mudler/luet/pkg/compiler"
"github.com/mudler/luet/pkg/config"
"github.com/mudler/luet/pkg/helpers"
. "github.com/mudler/luet/pkg/logger"
@@ -54,7 +56,12 @@ func (*SimpleDocker) BuildImage(opts compiler.CompilerBackendOptions) error {
}
Info(":whale: Building image " + name + " done")
//Info(string(out))
if config.LuetCfg.GetGeneral().ShowBuildOutput {
Info(string(out))
} else {
Debug(string(out))
}
return nil
}
@@ -82,6 +89,19 @@ func (*SimpleDocker) DownloadImage(opts compiler.CompilerBackendOptions) error {
return nil
}
func (*SimpleDocker) ImageExists(imagename string) bool {
buildarg := []string{"inspect", "--type=image", imagename}
Debug(":whale: Checking existance of docker image: " + imagename)
cmd := exec.Command("docker", buildarg...)
out, err := cmd.CombinedOutput()
if err != nil {
Warning("Image not present")
Debug(string(out))
return false
}
return true
}
func (*SimpleDocker) RemoveImage(opts compiler.CompilerBackendOptions) error {
name := opts.ImageName
buildarg := []string{"rmi", name}
@@ -94,6 +114,18 @@ func (*SimpleDocker) RemoveImage(opts compiler.CompilerBackendOptions) error {
return nil
}
func (*SimpleDocker) Push(opts compiler.CompilerBackendOptions) error {
name := opts.ImageName
pusharg := []string{"push", name}
out, err := exec.Command("docker", pusharg...).CombinedOutput()
if err != nil {
return errors.Wrap(err, "Failed pushing image: "+string(out))
}
Info(":whale: Pushed image:", name)
//Info(string(out))
return nil
}
func (s *SimpleDocker) ImageDefinitionToTar(opts compiler.CompilerBackendOptions) error {
if err := s.BuildImage(opts); err != nil {
return errors.Wrap(err, "Failed building image")
@@ -189,51 +221,20 @@ func (*SimpleDocker) ExtractRootfs(opts compiler.CompilerBackendOptions, keepPer
return nil
}
// container-diff diff daemon://luet/base alpine --type=file -j
// [
// {
// "Image1": "luet/base",
// "Image2": "alpine",
// "DiffType": "File",
// "Diff": {
// "Adds": null,
// "Dels": [
// {
// "Name": "/luetbuild",
// "Size": 5830706
// },
// {
// "Name": "/luetbuild/Dockerfile",
// "Size": 50
// },
// {
// "Name": "/luetbuild/output1",
// "Size": 5830656
// }
// ],
// "Mods": null
// }
// }
// ]
// Changes uses container-diff (https://github.com/GoogleContainerTools/container-diff) for retrieving out layer diffs
func (*SimpleDocker) Changes(fromImage, toImage string) ([]compiler.ArtifactLayer, error) {
tmpdiffs, err := ioutil.TempDir(os.TempDir(), "tmpdiffs")
if err != nil {
return []compiler.ArtifactLayer{}, errors.Wrap(err, "Error met while creating tempdir for rootfs")
}
defer os.RemoveAll(tmpdiffs) // clean up
// Changes retrieves changes between image layers
func (d *SimpleDocker) Changes(fromImage, toImage string) ([]compiler.ArtifactLayer, error) {
diffs, err := GenerateChanges(d, fromImage, toImage)
diffargs := []string{"diff", fromImage, toImage, "-v", "error", "-q", "--type=file", "-j", "-n", "-c", tmpdiffs}
out, err := exec.Command("container-diff", diffargs...).Output()
if err != nil {
return []compiler.ArtifactLayer{}, errors.Wrap(err, "Failed Resolving layer diffs: "+string(out))
if config.LuetCfg.GetGeneral().Debug {
summary := compiler.ComputeArtifactLayerSummary(diffs)
for _, l := range summary.Layers {
Debug(fmt.Sprintf("Diff %s -> %s: add %d (%d bytes), del %d (%d bytes), change %d (%d bytes)",
l.FromImage, l.ToImage,
l.AddFiles, l.AddSizes,
l.DelFiles, l.DelSizes,
l.ChangeFiles, l.ChangeSizes))
}
}
var diffs []compiler.ArtifactLayer
err = json.Unmarshal(out, &diffs)
if err != nil {
return []compiler.ArtifactLayer{}, errors.Wrap(err, "Failed unmarshalling json response: "+string(out))
}
return diffs, nil
return diffs, err
}

View File

@@ -40,7 +40,7 @@ var _ = Describe("Docker backend", func() {
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(1))
compiler := NewLuetCompiler(nil, generalRecipe.GetDatabase())
compiler := NewLuetCompiler(nil, generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "enman", Category: "app-admin", Version: "1.4.0"})
Expect(err).ToNot(HaveOccurred())

View File

@@ -97,6 +97,13 @@ func (*SimpleImg) CopyImage(src, dst string) error {
return nil
}
func (*SimpleImg) ImageExists(imagename string) bool {
// NOOP: not implemented
// TODO: Since img doesn't have an inspect command,
// we need to parse the ls output manually
return false
}
func (s *SimpleImg) ImageDefinitionToTar(opts compiler.CompilerBackendOptions) error {
if err := s.BuildImage(opts); err != nil {
return errors.Wrap(err, "Failed building image")
@@ -142,6 +149,18 @@ func (*SimpleImg) ExtractRootfs(opts compiler.CompilerBackendOptions, keepPerms
// TODO: Use container-diff (https://github.com/GoogleContainerTools/container-diff) for checking out layer diffs
// Changes uses container-diff (https://github.com/GoogleContainerTools/container-diff) for retrieving out layer diffs
func (*SimpleImg) Changes(fromImage, toImage string) ([]compiler.ArtifactLayer, error) {
return NewSimpleDockerBackend().Changes(fromImage, toImage)
func (i *SimpleImg) Changes(fromImage, toImage string) ([]compiler.ArtifactLayer, error) {
return GenerateChanges(i, fromImage, toImage)
}
func (*SimpleImg) Push(opts compiler.CompilerBackendOptions) error {
name := opts.ImageName
pusharg := []string{"push", name}
out, err := exec.Command("img", pusharg...).CombinedOutput()
if err != nil {
return errors.Wrap(err, "Failed pushing image: "+string(out))
}
Info(":tea: Pushed image:", name)
//Info(string(out))
return nil
}

78
pkg/compiler/checksum.go Normal file
View File

@@ -0,0 +1,78 @@
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package compiler
import (
//"strconv"
"crypto/sha256"
"fmt"
"hash"
"io"
"os"
// . "github.com/mudler/luet/pkg/logger"
"github.com/pkg/errors"
)
type HashImplementation string
const (
SHA256 HashImplementation = "sha256"
)
type Checksums map[string]string
type HashOptions struct {
Hasher hash.Hash
Type HashImplementation
}
// Generate generates all Checksums supported for the artifact
func (c *Checksums) Generate(a Artifact) error {
return c.generateSHA256(a)
}
func (c Checksums) Compare(d Checksums) error {
for t, sum := range d {
if v, ok := c[t]; ok && v != sum {
return errors.New("Checksum mismsatch")
}
}
return nil
}
func (c *Checksums) generateSHA256(a Artifact) error {
return c.generateSum(a, HashOptions{Hasher: sha256.New(), Type: SHA256})
}
func (c *Checksums) generateSum(a Artifact, opts HashOptions) error {
f, err := os.Open(a.GetPath())
if err != nil {
return err
}
defer f.Close()
if _, err := io.Copy(opts.Hasher, f); err != nil {
return err
}
sum := fmt.Sprintf("%x", opts.Hasher.Sum(nil))
(*c)[string(opts.Type)] = sum
return nil
}

View File

@@ -0,0 +1,61 @@
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package compiler_test
import (
"io/ioutil"
"os"
. "github.com/mudler/luet/pkg/compiler"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Checksum", func() {
Context("Generation", func() {
It("Compares successfully", func() {
tmpdir, err := ioutil.TempDir("", "tree")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(tmpdir) // clean up
buildsum := Checksums{}
definitionsum := Checksums{}
definitionsum2 := Checksums{}
Expect(len(buildsum)).To(Equal(0))
Expect(len(definitionsum)).To(Equal(0))
Expect(len(definitionsum2)).To(Equal(0))
err = buildsum.Generate(NewPackageArtifact("../../tests/fixtures/layers/alpine/build.yaml"))
Expect(err).ToNot(HaveOccurred())
err = definitionsum.Generate(NewPackageArtifact("../../tests/fixtures/layers/alpine/definition.yaml"))
Expect(err).ToNot(HaveOccurred())
err = definitionsum2.Generate(NewPackageArtifact("../../tests/fixtures/layers/alpine/definition.yaml"))
Expect(err).ToNot(HaveOccurred())
Expect(len(buildsum)).To(Equal(1))
Expect(len(definitionsum)).To(Equal(1))
Expect(len(definitionsum2)).To(Equal(1))
Expect(definitionsum.Compare(buildsum)).To(HaveOccurred())
Expect(definitionsum.Compare(definitionsum2)).ToNot(HaveOccurred())
})
})
})

View File

@@ -20,9 +20,13 @@ import (
"io/ioutil"
"os"
"path/filepath"
"github.com/ghodss/yaml"
"regexp"
"strings"
"sync"
"time"
"github.com/mudler/luet/pkg/helpers"
. "github.com/mudler/luet/pkg/logger"
@@ -33,16 +37,20 @@ import (
)
const BuildFile = "build.yaml"
const DefinitionFile = "definition.yaml"
type LuetCompiler struct {
*tree.CompilerRecipe
Backend CompilerBackend
Database pkg.PackageDatabase
ImageRepository string
PullFirst, KeepImg bool
Backend CompilerBackend
Database pkg.PackageDatabase
ImageRepository string
PullFirst, KeepImg, Clean bool
Concurrency int
CompressionType CompressionImplementation
Options CompilerOptions
}
func NewLuetCompiler(backend CompilerBackend, db pkg.PackageDatabase) Compiler {
func NewLuetCompiler(backend CompilerBackend, db pkg.PackageDatabase, opt *CompilerOptions) Compiler {
// The CompilerRecipe will gives us a tree with only build deps listed.
return &LuetCompiler{
Backend: backend,
@@ -50,12 +58,24 @@ func NewLuetCompiler(backend CompilerBackend, db pkg.PackageDatabase) Compiler {
tree.Recipe{Database: db},
},
Database: db,
ImageRepository: "luet/cache",
PullFirst: true,
KeepImg: true,
ImageRepository: opt.ImageRepository,
PullFirst: opt.PullFirst,
CompressionType: opt.CompressionType,
KeepImg: opt.KeepImg,
Concurrency: opt.Concurrency,
Clean: opt.Clean,
Options: *opt,
}
}
func (cs *LuetCompiler) SetConcurrency(i int) {
cs.Concurrency = i
}
func (cs *LuetCompiler) SetCompressionType(t CompressionImplementation) {
cs.CompressionType = t
}
func (cs *LuetCompiler) compilerWorker(i int, wg *sync.WaitGroup, cspecs chan CompilationSpec, a *[]Artifact, m *sync.Mutex, concurrency int, keepPermissions bool, errors chan error) {
defer wg.Done()
@@ -70,8 +90,9 @@ func (cs *LuetCompiler) compilerWorker(i int, wg *sync.WaitGroup, cspecs chan Co
m.Unlock()
}
}
func (cs *LuetCompiler) CompileWithReverseDeps(concurrency int, keepPermissions bool, ps CompilationSpecs) ([]Artifact, []error) {
artifacts, err := cs.CompileParallel(concurrency, keepPermissions, ps)
func (cs *LuetCompiler) CompileWithReverseDeps(keepPermissions bool, ps CompilationSpecs) ([]Artifact, []error) {
artifacts, err := cs.CompileParallel(keepPermissions, ps)
if len(err) != 0 {
return artifacts, err
}
@@ -119,11 +140,11 @@ func (cs *LuetCompiler) CompileWithReverseDeps(concurrency int, keepPermissions
Info(" :arrow_right_hook:", u.GetPackage().GetName(), ":leaves:", u.GetPackage().GetVersion(), "(", u.GetPackage().GetCategory(), ")")
}
artifacts2, err := cs.CompileParallel(concurrency, keepPermissions, uniques)
artifacts2, err := cs.CompileParallel(keepPermissions, uniques)
return append(artifacts, artifacts2...), err
}
func (cs *LuetCompiler) CompileParallel(concurrency int, keepPermissions bool, ps CompilationSpecs) ([]Artifact, []error) {
func (cs *LuetCompiler) CompileParallel(keepPermissions bool, ps CompilationSpecs) ([]Artifact, []error) {
Spinner(22)
defer SpinnerStop()
all := make(chan CompilationSpec)
@@ -131,9 +152,9 @@ func (cs *LuetCompiler) CompileParallel(concurrency int, keepPermissions bool, p
mutex := &sync.Mutex{}
errors := make(chan error, ps.Len())
var wg = new(sync.WaitGroup)
for i := 0; i < concurrency; i++ {
for i := 0; i < cs.Concurrency; i++ {
wg.Add(1)
go cs.compilerWorker(i, wg, all, &artifacts, mutex, concurrency, keepPermissions, errors)
go cs.compilerWorker(i, wg, all, &artifacts, mutex, cs.Concurrency, keepPermissions, errors)
}
for _, p := range ps.All() {
@@ -212,9 +233,39 @@ func (cs *LuetCompiler) stripIncludesFromRootfs(includes []string, rootfs string
return nil
}
func (cs *LuetCompiler) compileWithImage(image, buildertaggedImage, packageImage string, concurrency int, keepPermissions, keepImg bool, p CompilationSpec) (Artifact, error) {
func (cs *LuetCompiler) compileWithImage(image, buildertaggedImage, packageImage string, concurrency int, keepPermissions, keepImg bool, p CompilationSpec, generateArtifact bool) (Artifact, error) {
pkgTag := ":package: " + p.GetPackage().GetName()
// Use packageImage as salt into the fp being used
// so the hash is unique also in cases where
// some package deps does have completely different
// depgraphs
// TODO: As the salt contains the packageImage ( in registry/organization/imagename:tag format)
// the images hashes are broken with registry mirrors.
// We should use the image tag, or pass by the package assertion hash which is unique
// and identifies the deptree of the package.
fp := p.GetPackage().HashFingerprint(packageImage)
if buildertaggedImage == "" {
buildertaggedImage = cs.ImageRepository + "-" + fp + "-builder"
Debug(pkgTag, "Creating intermediary image", buildertaggedImage, "from", image)
}
// TODO: Cleanup, not actually hit
if packageImage == "" {
packageImage = cs.ImageRepository + "-" + fp
}
if !cs.Clean {
exists := cs.Backend.ImageExists(buildertaggedImage) && cs.Backend.ImageExists(packageImage)
if art, err := LoadArtifactFromYaml(p); err == nil && (cs.Options.SkipIfMetadataExists || exists) {
Debug("Artifact reloaded. Skipping build")
return art, err
}
}
p.SetSeedImage(image) // In this case, we ignore the build deps as we suppose that the image has them - otherwise we recompose the tree with a solver,
// and we build all the images first.
@@ -234,17 +285,13 @@ func (cs *LuetCompiler) compileWithImage(image, buildertaggedImage, packageImage
return nil, errors.Wrap(err, "Could not copy package sources")
}
if buildertaggedImage == "" {
buildertaggedImage = cs.ImageRepository + "-" + p.GetPackage().GetFingerPrint() + "-builder"
}
if packageImage == "" {
packageImage = cs.ImageRepository + "-" + p.GetPackage().GetFingerPrint()
}
if cs.PullFirst {
//Best effort pull
cs.Backend.DownloadImage(CompilerBackendOptions{ImageName: buildertaggedImage})
cs.Backend.DownloadImage(CompilerBackendOptions{ImageName: packageImage})
// Copy file into the build context, the compilespec might have requested to do so.
if len(p.GetRetrieve()) > 0 {
err := p.CopyRetrieves(buildDir)
if err != nil {
Warning("Failed copying retrieves", err.Error())
}
}
Info(pkgTag, "Generating :whale: definition for builder image from", image)
@@ -258,16 +305,32 @@ func (cs *LuetCompiler) compileWithImage(image, buildertaggedImage, packageImage
Destination: p.Rel(p.GetPackage().GetFingerPrint() + "-builder.image.tar"),
}
err = cs.Backend.BuildImage(builderOpts)
if err != nil {
return nil, errors.Wrap(err, "Could not build image: "+image+" "+builderOpts.DockerFileName)
buildBuilderImage := true
if cs.Options.PullFirst {
if err := cs.Backend.DownloadImage(builderOpts); err == nil {
buildBuilderImage = false
}
}
err = cs.Backend.ExportImage(builderOpts)
if err != nil {
if buildBuilderImage {
if err = cs.Backend.BuildImage(builderOpts); err != nil {
return nil, errors.Wrap(err, "Could not build image: "+image+" "+builderOpts.DockerFileName)
}
}
if err = cs.Backend.ExportImage(builderOpts); err != nil {
return nil, errors.Wrap(err, "Could not export image")
}
if !cs.Options.KeepImageExport {
defer os.Remove(builderOpts.Destination)
}
if cs.Options.Push && buildBuilderImage {
if err = cs.Backend.Push(builderOpts); err != nil {
return nil, errors.Wrap(err, "Could not push image: "+image+" "+builderOpts.DockerFileName)
}
}
// Then we write the step image, which uses the builder one
p.WriteStepImageDefinition(buildertaggedImage, filepath.Join(buildDir, p.GetPackage().GetFingerPrint()+".dockerfile"))
runnerOpts := CompilerBackendOptions{
@@ -277,31 +340,44 @@ func (cs *LuetCompiler) compileWithImage(image, buildertaggedImage, packageImage
Destination: p.Rel(p.GetPackage().GetFingerPrint() + ".image.tar"),
}
// if !keepPackageImg {
// err = cs.Backend.ImageDefinitionToTar(runnerOpts)
// if err != nil {
// return nil, errors.Wrap(err, "Could not export image to tar")
// }
// } else {
if err := cs.Backend.BuildImage(runnerOpts); err != nil {
return nil, errors.Wrap(err, "Failed building image for "+runnerOpts.ImageName+" "+runnerOpts.DockerFileName)
buildPackageImage := true
if cs.Options.PullFirst {
//Best effort pull
if err := cs.Backend.DownloadImage(runnerOpts); err == nil {
buildPackageImage = false
}
}
if buildPackageImage {
if err := cs.Backend.BuildImage(runnerOpts); err != nil {
return nil, errors.Wrap(err, "Failed building image for "+runnerOpts.ImageName+" "+runnerOpts.DockerFileName)
}
}
if err := cs.Backend.ExportImage(runnerOpts); err != nil {
return nil, errors.Wrap(err, "Failed exporting image")
}
// }
var diffs []ArtifactLayer
var artifact Artifact
if !cs.Options.KeepImageExport {
defer os.Remove(runnerOpts.Destination)
}
if !p.ImageUnpack() {
// we have to get diffs only if spec is not unpacked
diffs, err = cs.Backend.Changes(p.Rel(p.GetPackage().GetFingerPrint()+"-builder.image.tar"), p.Rel(p.GetPackage().GetFingerPrint()+".image.tar"))
if cs.Options.Push && buildPackageImage {
err = cs.Backend.Push(runnerOpts)
if err != nil {
return nil, errors.Wrap(err, "Could not generate changes from layers")
return nil, errors.Wrap(err, "Could not push image: "+image+" "+builderOpts.DockerFileName)
}
}
var artifact Artifact
unpack := p.ImageUnpack()
// If package_dir was specified in the spec, we want to treat the content of the directory
// as the root of our archive. ImageUnpack is implied to be true. override it
if p.GetPackageDir() != "" {
unpack = true
}
rootfs, err := ioutil.TempDir(p.GetOutputPath(), "rootfs")
if err != nil {
return nil, errors.Wrap(err, "Could not create tempdir")
@@ -321,134 +397,138 @@ func (cs *LuetCompiler) compileWithImage(image, buildertaggedImage, packageImage
// TODO: Handle caching and optionally do not remove things
err = cs.Backend.RemoveImage(builderOpts)
if err != nil {
// TODO: Have a --fatal flag which enables Warnings to exit.
Warning("Could not remove image ", builderOpts.ImageName)
// return nil, errors.Wrap(err, "Could not remove image")
}
err = cs.Backend.RemoveImage(runnerOpts)
if err != nil {
// TODO: Have a --fatal flag which enables Warnings to exit.
Warning("Could not remove image ", builderOpts.ImageName)
// return nil, errors.Wrap(err, "Could not remove image")
}
}
if p.ImageUnpack() {
if !generateArtifact {
return &PackageArtifact{}, nil
}
if unpack {
if p.GetPackageDir() != "" {
Info(":tophat: Packing from output dir", p.GetPackageDir())
rootfs = filepath.Join(rootfs, p.GetPackageDir())
}
if len(p.GetIncludes()) > 0 {
// strip from includes
cs.stripIncludesFromRootfs(p.GetIncludes(), rootfs)
}
artifact = NewPackageArtifact(p.Rel(p.GetPackage().GetFingerPrint() + ".package.tar"))
artifact.SetCompressionType(cs.CompressionType)
err = helpers.Tar(rootfs, p.Rel(p.GetPackage().GetFingerPrint()+".package.tar"))
err = artifact.Compress(rootfs, concurrency)
if err != nil {
return nil, errors.Wrap(err, "Error met while creating package archive")
}
artifact = NewPackageArtifact(p.Rel(p.GetPackage().GetFingerPrint() + ".package.tar"))
artifact.SetCompileSpec(p)
} else {
Info(pkgTag, "Generating delta")
artifact, err = ExtractArtifactFromDelta(rootfs, p.Rel(p.GetPackage().GetFingerPrint()+".package.tar"), diffs, concurrency, keepPermissions, p.GetIncludes())
diffs, err := cs.Backend.Changes(p.Rel(p.GetPackage().GetFingerPrint()+"-builder.image.tar"), p.Rel(p.GetPackage().GetFingerPrint()+".image.tar"))
if err != nil {
return nil, errors.Wrap(err, "Could not generate changes from layers")
}
artifact, err = ExtractArtifactFromDelta(rootfs, p.Rel(p.GetPackage().GetFingerPrint()+".package.tar"), diffs, concurrency, keepPermissions, p.GetIncludes(), cs.CompressionType)
if err != nil {
return nil, errors.Wrap(err, "Could not generate deltas")
}
artifact.SetCompileSpec(p)
}
filelist, err := artifact.FileList()
if err != nil {
return artifact, errors.Wrap(err, "Failed getting package list")
}
artifact.SetFiles(filelist)
artifact.GetCompileSpec().GetPackage().SetBuildTimestamp(time.Now().String())
err = artifact.WriteYaml(p.GetOutputPath())
if err != nil {
return artifact, err
return artifact, errors.Wrap(err, "Failed while writing metadata file")
}
Info(pkgTag, " :white_check_mark: Done")
return artifact, nil
}
func (cs *LuetCompiler) packageFromImage(p CompilationSpec, tag string, keepPermissions, keepImg bool) (Artifact, error) {
pkgTag := ":package: " + p.GetPackage().GetName()
func (cs *LuetCompiler) FromDatabase(db pkg.PackageDatabase, minimum bool, dst string) ([]CompilationSpec, error) {
compilerSpecs := NewLuetCompilationspecs()
Info(pkgTag, " 🍩 Build starts 🔨 🔨 🔨 ")
w := db.World()
builderOpts := CompilerBackendOptions{
ImageName: p.GetImage(),
Destination: p.Rel(p.GetPackage().GetFingerPrint() + ".image.tar"),
}
err := cs.Backend.DownloadImage(builderOpts)
if err != nil {
return nil, errors.Wrap(err, "Could not download image")
}
if tag != "" {
err = cs.Backend.CopyImage(p.GetImage(), tag)
for _, p := range w {
spec, err := cs.FromPackage(p)
if err != nil {
return nil, errors.Wrap(err, "Could not download image")
return nil, err
}
if dst != "" {
spec.SetOutputPath(dst)
}
compilerSpecs.Add(spec)
}
switch minimum {
case true:
return cs.ComputeMinimumCompilableSet(compilerSpecs.Unique().All()...)
default:
return compilerSpecs.Unique().All(), nil
}
}
// ComputeMinimumCompilableSet strips specs that are eventually compiled by leafs
func (cs *LuetCompiler) ComputeMinimumCompilableSet(p ...CompilationSpec) ([]CompilationSpec, error) {
// Generate a set with all the deps of the provided specs
// we will use that set to remove the deps from the list of provided compilation specs
allDependencies := solver.PackagesAssertions{} // Get all packages that will be in deps
result := []CompilationSpec{}
for _, spec := range p {
ass, err := cs.ComputeDepTree(spec)
if err != nil {
return result, errors.Wrap(err, "computin specs deptree")
}
allDependencies = append(allDependencies, ass.Drop(spec.GetPackage())...)
}
for _, spec := range p {
if found := allDependencies.Search(spec.GetPackage().GetFingerPrint()); found == nil {
result = append(result, spec)
}
}
err = cs.Backend.ExportImage(builderOpts)
if err != nil {
return nil, errors.Wrap(err, "Could not export image")
}
rootfs, err := ioutil.TempDir(p.GetOutputPath(), "rootfs")
if err != nil {
return nil, errors.Wrap(err, "Could not create tempdir")
}
defer os.RemoveAll(rootfs) // clean up
// TODO: Compression and such
err = cs.Backend.ExtractRootfs(CompilerBackendOptions{
ImageName: p.GetImage(),
SourcePath: builderOpts.Destination, Destination: rootfs}, keepPermissions)
if err != nil {
return nil, errors.Wrap(err, "Could not extract rootfs")
}
err = helpers.Tar(rootfs, p.Rel(p.GetPackage().GetFingerPrint()+".package.tar"))
if err != nil {
return nil, errors.Wrap(err, "Error met while creating package archive")
}
if !keepImg {
// We keep them around, so to not reload them from the tar (which should be the "correct way") and we automatically share the same layers
// TODO: Handle caching and optionally do not remove things
err = cs.Backend.RemoveImage(builderOpts)
if err != nil {
// TODO: Have a --fatal flag which enables Warnings to exit.
Warning("Could not remove image ", builderOpts.ImageName)
// return nil, errors.Wrap(err, "Could not remove image")
}
}
Info(pkgTag, " :white_check_mark: Done")
artifact := NewPackageArtifact(p.Rel(p.GetPackage().GetFingerPrint() + ".package.tar"))
artifact.SetCompileSpec(p)
err = artifact.WriteYaml(p.GetOutputPath())
if err != nil {
return artifact, err
}
return artifact, nil
return result, nil
}
func (cs *LuetCompiler) ComputeDepTree(p CompilationSpec) (solver.PackagesAssertions, error) {
s := solver.NewSolver(pkg.NewInMemoryDatabase(false), cs.Database, pkg.NewInMemoryDatabase(false))
s := solver.NewResolver(pkg.NewInMemoryDatabase(false), cs.Database, pkg.NewInMemoryDatabase(false), cs.Options.SolverOptions.Resolver())
solution, err := s.Install([]pkg.Package{p.GetPackage()})
solution, err := s.Install(pkg.Packages{p.GetPackage()})
if err != nil {
return nil, errors.Wrap(err, "While computing a solution for "+p.GetPackage().GetName())
return nil, errors.Wrap(err, "While computing a solution for "+p.GetPackage().HumanReadableString())
}
dependencies := solution.Order(cs.Database, p.GetPackage().GetFingerPrint())
dependencies, err := solution.Order(cs.Database, p.GetPackage().GetFingerPrint())
if err != nil {
return nil, errors.Wrap(err, "While order a solution for "+p.GetPackage().HumanReadableString())
}
assertions := solver.PackagesAssertions{}
for _, assertion := range dependencies { //highly dependent on the order
if assertion.Value {
nthsolution := dependencies.Cut(assertion.Package)
assertion.Hash = solver.PackageHash{
BuildHash: nthsolution.Drop(assertion.Package).AssertionHash(),
BuildHash: nthsolution.HashFrom(assertion.Package),
PackageHash: nthsolution.AssertionHash(),
}
assertions = append(assertions, assertion)
@@ -459,31 +539,31 @@ func (cs *LuetCompiler) ComputeDepTree(p CompilationSpec) (solver.PackagesAssert
}
// Compile is non-parallel
func (cs *LuetCompiler) Compile(concurrency int, keepPermissions bool, p CompilationSpec) (Artifact, error) {
func (cs *LuetCompiler) Compile(keepPermissions bool, p CompilationSpec) (Artifact, error) {
asserts, err := cs.ComputeDepTree(p)
if err != nil {
panic(err)
}
p.SetSourceAssertion(asserts)
return cs.compile(concurrency, keepPermissions, p)
return cs.compile(cs.Concurrency, keepPermissions, p)
}
func (cs *LuetCompiler) compile(concurrency int, keepPermissions bool, p CompilationSpec) (Artifact, error) {
Info(":package: Compiling", p.GetPackage().GetName(), "version", p.GetPackage().GetVersion(), ".... :coffee:")
Info(":package: Compiling", p.GetPackage().HumanReadableString(), ".... :coffee:")
if len(p.GetPackage().GetRequires()) == 0 && p.GetImage() == "" {
Error("Package with no deps and no seed image supplied, bailing out")
return nil, errors.New("Package " + p.GetPackage().GetFingerPrint() + "with no deps and no seed image supplied, bailing out")
return nil, errors.New("Package " + p.GetPackage().GetFingerPrint() +
" with no deps and no seed image supplied, bailing out")
}
targetAssertion := p.GetSourceAssertion().Search(p.GetPackage().GetFingerPrint())
targetPackageHash := cs.ImageRepository + ":" + targetAssertion.Hash.PackageHash
// - If image is set we just generate a plain dockerfile
// Treat last case (easier) first. The image is provided and we just compute a plain dockerfile with the images listed as above
if p.GetImage() != "" {
if p.ImageUnpack() { // If it is just an entire image, create a package from it
return cs.packageFromImage(p, "", keepPermissions, cs.KeepImg)
}
return cs.compileWithImage(p.GetImage(), "", "", concurrency, keepPermissions, cs.KeepImg, p)
return cs.compileWithImage(p.GetImage(), "", targetPackageHash, concurrency, keepPermissions, cs.KeepImg, p, true)
}
// - If image is not set, we read a base_image. Then we will build one image from it to kick-off our build based
@@ -497,76 +577,73 @@ func (cs *LuetCompiler) compile(concurrency int, keepPermissions bool, p Compila
depsN := 0
currentN := 0
Info(":deciduous_tree: Build dependencies for " + p.GetPackage().GetName())
for _, assertion := range dependencies { //highly dependent on the order
depsN++
Info(" :arrow_right_hook:", assertion.Package.GetName(), ":leaves:", assertion.Package.GetVersion(), "(", assertion.Package.GetCategory(), ")")
}
for _, assertion := range dependencies { //highly dependent on the order
currentN++
pkgTag := fmt.Sprintf(":package: %d/%d %s ⤑ %s", currentN, depsN, p.GetPackage().GetName(), assertion.Package.GetName())
Info(pkgTag, " :zap: Building dependency")
compileSpec, err := cs.FromPackage(assertion.Package)
if err != nil {
return nil, errors.Wrap(err, "Error while generating compilespec for "+assertion.Package.GetName())
packageDeps := !cs.Options.PackageTargetOnly
if !cs.Options.NoDeps {
Info(":deciduous_tree: Build dependencies for " + p.GetPackage().HumanReadableString())
for _, assertion := range dependencies { //highly dependent on the order
depsN++
Info(" :arrow_right_hook:", assertion.Package.HumanReadableString(), ":leaves:")
}
compileSpec.SetOutputPath(p.GetOutputPath())
buildImageHash := cs.ImageRepository + ":" + assertion.Hash.BuildHash
currentPackageImageHash := cs.ImageRepository + ":" + assertion.Hash.PackageHash
Debug(pkgTag, " :arrow_right_hook: :whale: Builder image from", buildImageHash)
Debug(pkgTag, " :arrow_right_hook: :whale: Package image name", currentPackageImageHash)
for _, assertion := range dependencies { //highly dependent on the order
currentN++
pkgTag := fmt.Sprintf(":package: %d/%d %s ⤑ %s", currentN, depsN, p.GetPackage().HumanReadableString(), assertion.Package.HumanReadableString())
Info(pkgTag, " :zap: Building dependency")
compileSpec, err := cs.FromPackage(assertion.Package)
if err != nil {
return nil, errors.Wrap(err, "Error while generating compilespec for "+assertion.Package.GetName())
}
compileSpec.SetOutputPath(p.GetOutputPath())
lastHash = currentPackageImageHash
if compileSpec.GetImage() != "" {
// TODO: Refactor this
if compileSpec.ImageUnpack() { // If it is just an entire image, create a package from it
if compileSpec.GetImage() == "" {
return nil, errors.New("No image defined for package: " + assertion.Package.GetName())
}
Info(pkgTag, ":whale: Sourcing package from image", compileSpec.GetImage())
artifact, err := cs.packageFromImage(compileSpec, currentPackageImageHash, keepPermissions, cs.KeepImg)
buildImageHash := cs.ImageRepository + ":" + assertion.Hash.BuildHash
currentPackageImageHash := cs.ImageRepository + ":" + assertion.Hash.PackageHash
Debug(pkgTag, " :arrow_right_hook: :whale: Builder image from", buildImageHash)
Debug(pkgTag, " :arrow_right_hook: :whale: Package image name", currentPackageImageHash)
lastHash = currentPackageImageHash
if compileSpec.GetImage() != "" {
Debug(pkgTag, " :wrench: Compiling "+compileSpec.GetPackage().HumanReadableString()+" from image")
artifact, err := cs.compileWithImage(compileSpec.GetImage(), buildImageHash, currentPackageImageHash, concurrency, keepPermissions, cs.KeepImg, compileSpec, packageDeps)
if err != nil {
return nil, errors.Wrap(err, "Failed compiling "+compileSpec.GetPackage().GetName())
return nil, errors.Wrap(err, "Failed compiling "+compileSpec.GetPackage().HumanReadableString())
}
departifacts = append(departifacts, artifact)
Info(pkgTag, ":white_check_mark: Done")
continue
}
Debug(pkgTag, " :wrench: Compiling "+compileSpec.GetPackage().GetFingerPrint()+" from image")
artifact, err := cs.compileWithImage(compileSpec.GetImage(), buildImageHash, currentPackageImageHash, concurrency, keepPermissions, cs.KeepImg, compileSpec)
Debug(pkgTag, " :wrench: Compiling "+compileSpec.GetPackage().HumanReadableString()+" from tree")
artifact, err := cs.compileWithImage(buildImageHash, "", currentPackageImageHash, concurrency, keepPermissions, cs.KeepImg, compileSpec, packageDeps)
if err != nil {
return nil, errors.Wrap(err, "Failed compiling "+compileSpec.GetPackage().GetName())
return nil, errors.Wrap(err, "Failed compiling "+compileSpec.GetPackage().HumanReadableString())
// deperrs = append(deperrs, err)
// break // stop at first error
}
departifacts = append(departifacts, artifact)
Info(pkgTag, ":white_check_mark: Done")
continue
Info(pkgTag, ":collision: Done")
}
Debug(pkgTag, " :wrench: Compiling "+compileSpec.GetPackage().GetFingerPrint()+" from tree")
artifact, err := cs.compileWithImage(buildImageHash, "", currentPackageImageHash, concurrency, keepPermissions, cs.KeepImg, compileSpec)
} else if len(dependencies) > 0 {
lastHash = cs.ImageRepository + ":" + dependencies[len(dependencies)-1].Hash.PackageHash
}
if !cs.Options.OnlyDeps {
Info(":package:", p.GetPackage().HumanReadableString(), ":cyclone: Building package target from:", lastHash)
artifact, err := cs.compileWithImage(lastHash, "", targetPackageHash, concurrency, keepPermissions, cs.KeepImg, p, true)
if err != nil {
return nil, errors.Wrap(err, "Failed compiling "+compileSpec.GetPackage().GetName())
// deperrs = append(deperrs, err)
// break // stop at first error
return artifact, err
}
departifacts = append(departifacts, artifact)
Info(pkgTag, ":collision: Done")
}
artifact.SetDependencies(departifacts)
artifact.SetSourceAssertion(p.GetSourceAssertion())
Info(":package:", p.GetPackage().GetName(), ":cyclone: Building package target from:", lastHash)
artifact, err := cs.compileWithImage(lastHash, "", "", concurrency, keepPermissions, cs.KeepImg, p)
if err != nil {
return artifact, err
} else {
return departifacts[len(departifacts)-1], nil
}
artifact.SetDependencies(departifacts)
artifact.SetSourceAssertion(p.GetSourceAssertion())
return artifact, err
}
type templatedata map[string]interface{}
func (cs *LuetCompiler) FromPackage(p pkg.Package) (CompilationSpec, error) {
pack, err := cs.Database.FindPackageCandidate(p)
@@ -578,12 +655,28 @@ func (cs *LuetCompiler) FromPackage(p pkg.Package) (CompilationSpec, error) {
if !helpers.Exists(buildFile) {
return nil, errors.New("No build file present for " + p.GetFingerPrint())
}
dat, err := ioutil.ReadFile(buildFile)
defFile := pack.Rel(DefinitionFile)
if !helpers.Exists(defFile) {
return nil, errors.New("No build file present for " + p.GetFingerPrint())
}
def, err := ioutil.ReadFile(defFile)
if err != nil {
return nil, err
}
return NewLuetCompilationSpec(dat, pack)
build, err := ioutil.ReadFile(buildFile)
if err != nil {
return nil, err
}
var values templatedata
if err = yaml.Unmarshal(def, &values); err != nil {
return nil, err
}
out, err := helpers.RenderHelm(string(build), values)
if err != nil {
return nil, err
}
return NewLuetCompilationSpec([]byte(out), pack)
}
func (cs *LuetCompiler) GetBackend() CompilerBackend {

View File

@@ -18,11 +18,15 @@ package compiler_test
import (
"testing"
. "github.com/mudler/luet/cmd"
config "github.com/mudler/luet/pkg/config"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestSolver(t *testing.T) {
RegisterFailHandler(Fail)
LoadConfig(config.LuetCfg)
RunSpecs(t, "Compiler Suite")
}

View File

@@ -38,7 +38,7 @@ var _ = Describe("Compiler", func() {
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(3))
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase())
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "b", Category: "test", Version: "1.0"})
Expect(err).ToNot(HaveOccurred())
@@ -53,7 +53,9 @@ var _ = Describe("Compiler", func() {
Expect(spec.GetPreBuildSteps()).To(Equal([]string{"echo foo > /test", "echo bar > /test2", "chmod +x generate.sh"}))
spec.SetOutputPath(tmpdir)
artifact, err := compiler.Compile(2, false, spec)
compiler.SetConcurrency(2)
artifact, err := compiler.Compile(false, spec)
Expect(err).ToNot(HaveOccurred())
Expect(helpers.Exists(artifact.GetPath())).To(BeTrue())
Expect(helpers.Untar(artifact.GetPath(), tmpdir, false)).ToNot(HaveOccurred())
@@ -80,7 +82,7 @@ var _ = Describe("Compiler", func() {
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(3))
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase())
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "b", Category: "test", Version: "1.0"})
Expect(err).ToNot(HaveOccurred())
@@ -95,7 +97,8 @@ var _ = Describe("Compiler", func() {
spec.SetOutputPath(tmpdir)
spec2.SetOutputPath(tmpdir)
artifacts, errs := compiler.CompileParallel(2, false, NewLuetCompilationspecs(spec, spec2))
compiler.SetConcurrency(2)
artifacts, errs := compiler.CompileParallel(false, NewLuetCompilationspecs(spec, spec2))
Expect(errs).To(BeNil())
for _, artifact := range artifacts {
Expect(helpers.Exists(artifact.GetPath())).To(BeTrue())
@@ -105,6 +108,27 @@ var _ = Describe("Compiler", func() {
})
})
Context("Templated packages",func(){
It("Renders", func() {
generalRecipe := tree.NewCompilerRecipe(pkg.NewInMemoryDatabase(false))
tmpdir, err := ioutil.TempDir("", "package")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(tmpdir) // clean up
err = generalRecipe.Load("../../tests/fixtures/templates")
Expect(err).ToNot(HaveOccurred())
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(1))
pkg ,err := generalRecipe.GetDatabase().FindPackage(&pkg.DefaultPackage{Name: "b", Category: "test", Version: "1.0"})
Expect(err).ToNot(HaveOccurred())
spec, err := compiler.FromPackage(pkg)
Expect(err).ToNot(HaveOccurred())
Expect(spec.GetImage()).To(Equal("b:bar"))
})
})
Context("Reconstruct image tree", func() {
It("Compiles it", func() {
generalRecipe := tree.NewCompilerRecipe(pkg.NewInMemoryDatabase(false))
@@ -117,7 +141,7 @@ var _ = Describe("Compiler", func() {
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(4))
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase())
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "c", Category: "test", Version: "1.0"})
Expect(err).ToNot(HaveOccurred())
@@ -134,8 +158,9 @@ var _ = Describe("Compiler", func() {
spec.SetOutputPath(tmpdir)
spec2.SetOutputPath(tmpdir)
spec3.SetOutputPath(tmpdir)
compiler.SetConcurrency(2)
artifacts, errs := compiler.CompileParallel(2, false, NewLuetCompilationspecs(spec, spec2, spec3))
artifacts, errs := compiler.CompileParallel(false, NewLuetCompilationspecs(spec, spec2, spec3))
Expect(errs).To(BeNil())
Expect(len(artifacts)).To(Equal(3))
@@ -173,7 +198,7 @@ var _ = Describe("Compiler", func() {
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(2))
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase())
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "extra", Category: "layer", Version: "1.0"})
Expect(err).ToNot(HaveOccurred())
@@ -181,12 +206,12 @@ var _ = Describe("Compiler", func() {
Expect(err).ToNot(HaveOccurred())
spec.SetOutputPath(tmpdir)
spec2.SetOutputPath(tmpdir)
artifacts, errs := compiler.CompileParallel(1, false, NewLuetCompilationspecs(spec))
compiler.SetConcurrency(1)
artifacts, errs := compiler.CompileParallel(false, NewLuetCompilationspecs(spec))
Expect(errs).To(BeNil())
Expect(len(artifacts)).To(Equal(1))
artifacts2, errs := compiler.CompileParallel(1, false, NewLuetCompilationspecs(spec2))
artifacts2, errs := compiler.CompileParallel(false, NewLuetCompilationspecs(spec2))
Expect(errs).To(BeNil())
Expect(len(artifacts2)).To(Equal(1))
@@ -215,7 +240,7 @@ var _ = Describe("Compiler", func() {
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(1))
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase())
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "b", Category: "test", Version: "1.0"})
Expect(err).ToNot(HaveOccurred())
@@ -224,8 +249,9 @@ var _ = Describe("Compiler", func() {
// Expect(err).ToNot(HaveOccurred())
spec.SetOutputPath(tmpdir)
compiler.SetConcurrency(1)
artifacts, errs := compiler.CompileParallel(1, false, NewLuetCompilationspecs(spec))
artifacts, errs := compiler.CompileParallel(false, NewLuetCompilationspecs(spec))
Expect(errs).To(BeNil())
Expect(len(artifacts)).To(Equal(1))
@@ -249,7 +275,7 @@ var _ = Describe("Compiler", func() {
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(2))
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase())
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "b", Category: "test", Version: "1.0"})
Expect(err).ToNot(HaveOccurred())
@@ -258,8 +284,8 @@ var _ = Describe("Compiler", func() {
// Expect(err).ToNot(HaveOccurred())
spec.SetOutputPath(tmpdir)
artifacts, errs := compiler.CompileParallel(1, false, NewLuetCompilationspecs(spec))
compiler.SetConcurrency(1)
artifacts, errs := compiler.CompileParallel(false, NewLuetCompilationspecs(spec))
Expect(errs).To(BeNil())
Expect(len(artifacts)).To(Equal(1))
@@ -287,7 +313,7 @@ var _ = Describe("Compiler", func() {
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(3))
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase())
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "pkgs-checker", Category: "package", Version: "9999"})
Expect(err).ToNot(HaveOccurred())
@@ -296,8 +322,9 @@ var _ = Describe("Compiler", func() {
// Expect(err).ToNot(HaveOccurred())
spec.SetOutputPath(tmpdir)
compiler.SetConcurrency(1)
artifacts, errs := compiler.CompileParallel(1, false, NewLuetCompilationspecs(spec))
artifacts, errs := compiler.CompileParallel(false, NewLuetCompilationspecs(spec))
Expect(errs).To(BeNil())
Expect(len(artifacts)).To(Equal(1))
@@ -327,7 +354,7 @@ var _ = Describe("Compiler", func() {
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(3))
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase())
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "d", Category: "test", Version: "1.0"})
Expect(err).ToNot(HaveOccurred())
@@ -336,8 +363,9 @@ var _ = Describe("Compiler", func() {
// Expect(err).ToNot(HaveOccurred())
spec.SetOutputPath(tmpdir)
compiler.SetConcurrency(1)
artifacts, errs := compiler.CompileParallel(1, false, NewLuetCompilationspecs(spec))
artifacts, errs := compiler.CompileParallel(false, NewLuetCompilationspecs(spec))
Expect(errs).To(BeNil())
Expect(len(artifacts)).To(Equal(1))
Expect(len(artifacts[0].GetDependencies())).To(Equal(1))
@@ -370,7 +398,7 @@ var _ = Describe("Compiler", func() {
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(3))
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase())
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "d", Category: "test", Version: "1.0"})
Expect(err).ToNot(HaveOccurred())
@@ -379,8 +407,9 @@ var _ = Describe("Compiler", func() {
// Expect(err).ToNot(HaveOccurred())
spec.SetOutputPath(tmpdir)
compiler.SetConcurrency(1)
artifacts, errs := compiler.CompileParallel(1, false, NewLuetCompilationspecs(spec))
artifacts, errs := compiler.CompileParallel(false, NewLuetCompilationspecs(spec))
Expect(errs).To(BeNil())
Expect(len(artifacts)).To(Equal(1))
Expect(len(artifacts[0].GetDependencies())).To(Equal(1))
@@ -411,7 +440,7 @@ var _ = Describe("Compiler", func() {
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(3))
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase())
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "extra", Category: "layer", Version: "0.1"})
Expect(err).ToNot(HaveOccurred())
@@ -421,7 +450,7 @@ var _ = Describe("Compiler", func() {
spec.SetOutputPath(tmpdir)
artifacts, errs := compiler.CompileWithReverseDeps(1, false, NewLuetCompilationspecs(spec))
artifacts, errs := compiler.CompileWithReverseDeps(false, NewLuetCompilationspecs(spec))
Expect(errs).To(BeNil())
Expect(len(artifacts)).To(Equal(2))
@@ -449,7 +478,7 @@ var _ = Describe("Compiler", func() {
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(10))
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase())
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "vhba", Category: "sys-fs-5.4.2", Version: "20190410"})
Expect(err).ToNot(HaveOccurred())
@@ -459,7 +488,7 @@ var _ = Describe("Compiler", func() {
spec.SetOutputPath(tmpdir)
artifacts, errs := compiler.CompileParallel(1, false, NewLuetCompilationspecs(spec))
artifacts, errs := compiler.CompileParallel(false, NewLuetCompilationspecs(spec))
Expect(errs).To(BeNil())
Expect(len(artifacts)).To(Equal(1))
Expect(len(artifacts[0].GetDependencies())).To(Equal(6))
@@ -488,13 +517,13 @@ var _ = Describe("Compiler", func() {
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(4))
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase())
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "b", Category: "test", Version: "1.0"})
spec.SetOutputPath(tmpdir)
artifacts, errs := compiler.CompileWithReverseDeps(1, false, NewLuetCompilationspecs(spec))
artifacts, errs := compiler.CompileWithReverseDeps(false, NewLuetCompilationspecs(spec))
Expect(errs).To(BeNil())
Expect(len(artifacts)).To(Equal(4))
@@ -540,7 +569,7 @@ var _ = Describe("Compiler", func() {
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(3))
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase())
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "c", Category: "test", Version: "1.0"})
Expect(err).ToNot(HaveOccurred())
@@ -552,7 +581,9 @@ var _ = Describe("Compiler", func() {
defer os.RemoveAll(tmpdir) // clean up
spec.SetOutputPath(tmpdir)
artifacts, errs := compiler.CompileParallel(2, false, NewLuetCompilationspecs(spec))
compiler.SetConcurrency(2)
artifacts, errs := compiler.CompileParallel(false, NewLuetCompilationspecs(spec))
Expect(errs).To(BeNil())
for _, artifact := range artifacts {
Expect(helpers.Exists(artifact.GetPath())).To(BeTrue())
@@ -581,7 +612,7 @@ var _ = Describe("Compiler", func() {
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(2))
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase())
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "runtime", Category: "layer", Version: "0.1"})
Expect(err).ToNot(HaveOccurred())
@@ -593,7 +624,9 @@ var _ = Describe("Compiler", func() {
defer os.RemoveAll(tmpdir) // clean up
spec.SetOutputPath(tmpdir)
artifacts, errs := compiler.CompileParallel(2, false, NewLuetCompilationspecs(spec))
compiler.SetConcurrency(1)
artifacts, errs := compiler.CompileParallel(false, NewLuetCompilationspecs(spec))
Expect(errs).To(BeNil())
Expect(len(artifacts)).To(Equal(1))
Expect(len(artifacts[0].GetDependencies())).To(Equal(1))
@@ -602,4 +635,153 @@ var _ = Describe("Compiler", func() {
Expect(helpers.Exists(spec.Rel("var"))).ToNot(BeTrue())
})
})
Context("Packages which conents are a package folder", func() {
It("Compiles it in parallel", func() {
generalRecipe := tree.NewCompilerRecipe(pkg.NewInMemoryDatabase(false))
err := generalRecipe.Load("../../tests/fixtures/package_dir")
Expect(err).ToNot(HaveOccurred())
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(2))
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{
Name: "dironly",
Category: "test",
Version: "1.0",
})
Expect(err).ToNot(HaveOccurred())
spec2, err := compiler.FromPackage(&pkg.DefaultPackage{
Name: "dironly_filter",
Category: "test",
Version: "1.0",
})
Expect(err).ToNot(HaveOccurred())
Expect(spec.GetPackage().GetPath()).ToNot(Equal(""))
tmpdir, err := ioutil.TempDir("", "tree")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(tmpdir) // clean up
tmpdir2, err := ioutil.TempDir("", "tree2")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(tmpdir2) // clean up
spec.SetOutputPath(tmpdir)
spec2.SetOutputPath(tmpdir2)
compiler.SetConcurrency(1)
artifacts, errs := compiler.CompileParallel(false, NewLuetCompilationspecs(spec, spec2))
Expect(errs).To(BeNil())
Expect(len(artifacts)).To(Equal(2))
Expect(len(artifacts[0].GetDependencies())).To(Equal(0))
Expect(helpers.Untar(spec.Rel("dironly-test-1.0.package.tar"), tmpdir, false)).ToNot(HaveOccurred())
Expect(helpers.Exists(spec.Rel("test1"))).To(BeTrue())
Expect(helpers.Exists(spec.Rel("test2"))).To(BeTrue())
Expect(helpers.Untar(spec2.Rel("dironly_filter-test-1.0.package.tar"), tmpdir2, false)).ToNot(HaveOccurred())
Expect(helpers.Exists(spec2.Rel("test5"))).To(BeTrue())
Expect(helpers.Exists(spec2.Rel("test6"))).ToNot(BeTrue())
Expect(helpers.Exists(spec2.Rel("artifact42"))).ToNot(BeTrue())
})
})
Context("Compression", func() {
It("Builds packages in gzip", func() {
generalRecipe := tree.NewCompilerRecipe(pkg.NewInMemoryDatabase(false))
err := generalRecipe.Load("../../tests/fixtures/packagelayers")
Expect(err).ToNot(HaveOccurred())
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(2))
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "runtime", Category: "layer", Version: "0.1"})
Expect(err).ToNot(HaveOccurred())
compiler.SetCompressionType(GZip)
Expect(spec.GetPackage().GetPath()).ToNot(Equal(""))
tmpdir, err := ioutil.TempDir("", "tree")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(tmpdir) // clean up
spec.SetOutputPath(tmpdir)
compiler.SetConcurrency(1)
artifacts, errs := compiler.CompileParallel(false, NewLuetCompilationspecs(spec))
Expect(errs).To(BeNil())
Expect(len(artifacts)).To(Equal(1))
Expect(len(artifacts[0].GetDependencies())).To(Equal(1))
Expect(helpers.Exists(spec.Rel("runtime-layer-0.1.package.tar.gz"))).To(BeTrue())
Expect(helpers.Exists(spec.Rel("runtime-layer-0.1.package.tar"))).To(BeFalse())
Expect(artifacts[0].Unpack(tmpdir, false)).ToNot(HaveOccurred())
// Expect(helpers.Untar(spec.Rel("runtime-layer-0.1.package.tar"), tmpdir, false)).ToNot(HaveOccurred())
Expect(helpers.Exists(spec.Rel("bin/busybox"))).To(BeTrue())
Expect(helpers.Exists(spec.Rel("var"))).ToNot(BeTrue())
})
})
Context("Compilation of whole tree", func() {
It("doesn't include dependencies that would be compiled anyway", func() {
// As some specs are dependent from each other, don't pull it in if they would
// be eventually
generalRecipe := tree.NewCompilerRecipe(pkg.NewInMemoryDatabase(false))
err := generalRecipe.Load("../../tests/fixtures/includeimage")
Expect(err).ToNot(HaveOccurred())
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(2))
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
specs, err := compiler.FromDatabase(generalRecipe.GetDatabase(), true, "")
Expect(err).ToNot(HaveOccurred())
Expect(len(specs)).To(Equal(1))
Expect(specs[0].GetPackage().GetFingerPrint()).To(Equal("b-test-1.0"))
})
})
Context("File list", func() {
It("is generated after the compilation process and annotated in the metadata", func() {
generalRecipe := tree.NewCompilerRecipe(pkg.NewInMemoryDatabase(false))
err := generalRecipe.Load("../../tests/fixtures/packagelayers")
Expect(err).ToNot(HaveOccurred())
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(2))
compiler := NewLuetCompiler(sd.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "runtime", Category: "layer", Version: "0.1"})
Expect(err).ToNot(HaveOccurred())
compiler.SetCompressionType(GZip)
Expect(spec.GetPackage().GetPath()).ToNot(Equal(""))
tmpdir, err := ioutil.TempDir("", "tree")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(tmpdir) // clean up
spec.SetOutputPath(tmpdir)
compiler.SetConcurrency(1)
artifacts, errs := compiler.CompileParallel(false, NewLuetCompilationspecs(spec))
Expect(errs).To(BeNil())
Expect(len(artifacts)).To(Equal(1))
Expect(len(artifacts[0].GetDependencies())).To(Equal(1))
Expect(artifacts[0].GetFiles()).To(ContainElement("bin/busybox"))
Expect(helpers.Exists(spec.Rel("runtime-layer-0.1.metadata.yaml"))).To(BeTrue())
art, err := LoadArtifactFromYaml(spec)
Expect(err).ToNot(HaveOccurred())
files := art.GetFiles()
Expect(files).To(ContainElement("bin/busybox"))
})
})
})

View File

@@ -16,20 +16,25 @@
package compiler
import (
"runtime"
"github.com/mudler/luet/pkg/config"
pkg "github.com/mudler/luet/pkg/package"
"github.com/mudler/luet/pkg/solver"
)
type Compiler interface {
Compile(int, bool, CompilationSpec) (Artifact, error)
CompileParallel(concurrency int, keepPermissions bool, ps CompilationSpecs) ([]Artifact, []error)
CompileWithReverseDeps(concurrency int, keepPermissions bool, ps CompilationSpecs) ([]Artifact, []error)
Compile(bool, CompilationSpec) (Artifact, error)
CompileParallel(keepPermissions bool, ps CompilationSpecs) ([]Artifact, []error)
CompileWithReverseDeps(keepPermissions bool, ps CompilationSpecs) ([]Artifact, []error)
ComputeDepTree(p CompilationSpec) (solver.PackagesAssertions, error)
ComputeMinimumCompilableSet(p ...CompilationSpec) ([]CompilationSpec, error)
SetConcurrency(i int)
FromPackage(pkg.Package) (CompilationSpec, error)
FromDatabase(db pkg.PackageDatabase, minimum bool, dst string) ([]CompilationSpec, error)
SetBackend(CompilerBackend)
GetBackend() CompilerBackend
SetCompressionType(t CompressionImplementation)
}
type CompilerBackendOptions struct {
@@ -39,6 +44,36 @@ type CompilerBackendOptions struct {
Destination string
}
type CompilerOptions struct {
ImageRepository string
PullFirst, KeepImg, Push bool
Concurrency int
CompressionType CompressionImplementation
Clean bool
KeepImageExport bool
OnlyDeps bool
NoDeps bool
SolverOptions config.LuetSolverOptions
SkipIfMetadataExists bool
PackageTargetOnly bool
}
func NewDefaultCompilerOptions() *CompilerOptions {
return &CompilerOptions{
ImageRepository: "luet/cache",
PullFirst: false,
Push: false,
CompressionType: None,
KeepImg: true,
Concurrency: runtime.NumCPU(),
Clean: true,
OnlyDeps: false,
NoDeps: false,
}
}
type CompilerBackend interface {
BuildImage(CompilerBackendOptions) error
ExportImage(CompilerBackendOptions) error
@@ -49,6 +84,10 @@ type CompilerBackend interface {
CopyImage(string, string) error
DownloadImage(opts CompilerBackendOptions) error
Push(opts CompilerBackendOptions) error
ImageExists(string) bool
}
type Artifact interface {
@@ -62,6 +101,18 @@ type Artifact interface {
SetCompileSpec(as CompilationSpec)
GetCompileSpec() CompilationSpec
WriteYaml(dst string) error
Unpack(dst string, keepPerms bool) error
Compress(src string, concurrency int) error
SetCompressionType(t CompressionImplementation)
FileList() ([]string, error)
Hash() error
Verify() error
SetFiles(f []string)
GetFiles() []string
GetChecksums() Checksums
SetChecksums(c Checksums)
}
type ArtifactNode struct {
@@ -78,6 +129,19 @@ type ArtifactLayer struct {
ToImage string `json:"Image2"`
Diffs ArtifactDiffs `json:"Diff"`
}
type ArtifactLayerSummary struct {
FromImage string `json:"image1"`
ToImage string `json:"image2"`
AddFiles int `json:"add_files"`
AddSizes int64 `json:"add_sizes"`
DelFiles int `json:"del_files"`
DelSizes int64 `json:"del_sizes"`
ChangeFiles int `json:"change_files"`
ChangeSizes int64 `json:"change_sizes"`
}
type ArtifactLayersSummary struct {
Layers []ArtifactLayerSummary `json:"summary"`
}
// CompilationSpec represent a compilation specification derived from a package
type CompilationSpec interface {
@@ -107,6 +171,12 @@ type CompilationSpec interface {
GetSourceAssertion() solver.PackagesAssertions
SetSourceAssertion(as solver.PackagesAssertions)
GetRetrieve() []string
CopyRetrieves(dest string) error
SetPackageDir(string)
GetPackageDir() string
}
type CompilationSpecs interface {

View File

@@ -21,6 +21,7 @@ import (
pkg "github.com/mudler/luet/pkg/package"
"github.com/mudler/luet/pkg/solver"
"github.com/otiai10/copy"
yaml "gopkg.in/yaml.v2"
)
@@ -94,6 +95,9 @@ type LuetCompilationSpec struct {
Seed string `json:"seed"`
Package *pkg.DefaultPackage `json:"package"`
SourceAssertion solver.PackagesAssertions `json:"-"`
PackageDir string `json:"package_dir" yaml:"package_dir"`
Retrieve []string `json:"retrieve"`
OutputPath string `json:"-"` // Where the build processfiles go
Unpack bool `json:"unpack"`
@@ -120,6 +124,14 @@ func (cs *LuetCompilationSpec) GetPackage() pkg.Package {
return cs.Package
}
func (cs *LuetCompilationSpec) GetPackageDir() string {
return cs.PackageDir
}
func (cs *LuetCompilationSpec) SetPackageDir(s string) {
cs.PackageDir = s
}
func (cs *LuetCompilationSpec) BuildSteps() []string {
return cs.Steps
}
@@ -136,6 +148,10 @@ func (cs *LuetCompilationSpec) GetIncludes() []string {
return cs.Includes
}
func (cs *LuetCompilationSpec) GetRetrieve() []string {
return cs.Retrieve
}
func (cs *LuetCompilationSpec) GetSeedImage() string {
return cs.Seed
}
@@ -164,6 +180,24 @@ func (cs *LuetCompilationSpec) SetSeedImage(s string) {
cs.Seed = s
}
func (cs *LuetCompilationSpec) CopyRetrieves(dest string) error {
var err error
if len(cs.Retrieve) > 0 {
for _, s := range cs.Retrieve {
matches, err := filepath.Glob(cs.Rel(s))
if err != nil {
continue
}
for _, m := range matches {
err = copy.Copy(m, filepath.Join(dest, filepath.Base(m)))
}
}
}
return err
}
// TODO: docker build image first. Then a backend can be used to actually spin up a container with it and run the steps within
func (cs *LuetCompilationSpec) RenderBuildImage() (string, error) {
spec := `
@@ -174,6 +208,19 @@ ENV PACKAGE_NAME=` + cs.Package.GetName() + `
ENV PACKAGE_VERSION=` + cs.Package.GetVersion() + `
ENV PACKAGE_CATEGORY=` + cs.Package.GetCategory()
if len(cs.Retrieve) > 0 {
for _, s := range cs.Retrieve {
//var file string
// if helpers.IsValidUrl(s) {
// file = s
// } else {
// file = cs.Rel(s)
// }
spec = spec + `
ADD ` + s + ` /luetbuild/`
}
}
for _, s := range cs.Env {
spec = spec + `
ENV ` + s

View File

@@ -61,7 +61,7 @@ var _ = Describe("Spec", func() {
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(1))
compiler := NewLuetCompiler(nil, generalRecipe.GetDatabase())
compiler := NewLuetCompiler(nil, generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "enman", Category: "app-admin", Version: "1.4.0"})
Expect(err).ToNot(HaveOccurred())
@@ -105,4 +105,74 @@ RUN echo bar > /test2`))
})
})
It("Renders retrieve and env fields", func() {
generalRecipe := tree.NewGeneralRecipe(pkg.NewInMemoryDatabase(false))
err := generalRecipe.Load("../../tests/fixtures/retrieve")
Expect(err).ToNot(HaveOccurred())
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(1))
compiler := NewLuetCompiler(nil, generalRecipe.GetDatabase(), NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "a", Category: "test", Version: "1.0"})
Expect(err).ToNot(HaveOccurred())
lspec, ok := spec.(*LuetCompilationSpec)
Expect(ok).To(BeTrue())
Expect(lspec.Steps).To(Equal([]string{"echo foo > /test", "echo bar > /test2"}))
Expect(lspec.Image).To(Equal("luet/base"))
Expect(lspec.Seed).To(Equal("alpine"))
tmpdir, err := ioutil.TempDir("", "tree")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(tmpdir) // clean up
err = lspec.WriteBuildImageDefinition(filepath.Join(tmpdir, "Dockerfile"))
Expect(err).ToNot(HaveOccurred())
dockerfile, err := helpers.Read(filepath.Join(tmpdir, "Dockerfile"))
Expect(err).ToNot(HaveOccurred())
Expect(dockerfile).To(Equal(`
FROM alpine
COPY . /luetbuild
WORKDIR /luetbuild
ENV PACKAGE_NAME=a
ENV PACKAGE_VERSION=1.0
ENV PACKAGE_CATEGORY=test
ADD test /luetbuild/
ADD http://www.google.com /luetbuild/
ENV test=1`))
lspec.SetOutputPath("/foo/bar")
err = lspec.WriteBuildImageDefinition(filepath.Join(tmpdir, "Dockerfile"))
Expect(err).ToNot(HaveOccurred())
dockerfile, err = helpers.Read(filepath.Join(tmpdir, "Dockerfile"))
Expect(err).ToNot(HaveOccurred())
Expect(dockerfile).To(Equal(`
FROM alpine
COPY . /luetbuild
WORKDIR /luetbuild
ENV PACKAGE_NAME=a
ENV PACKAGE_VERSION=1.0
ENV PACKAGE_CATEGORY=test
ADD test /luetbuild/
ADD http://www.google.com /luetbuild/
ENV test=1`))
err = lspec.WriteStepImageDefinition(lspec.Image, filepath.Join(tmpdir, "Dockerfile"))
Expect(err).ToNot(HaveOccurred())
dockerfile, err = helpers.Read(filepath.Join(tmpdir, "Dockerfile"))
Expect(err).ToNot(HaveOccurred())
Expect(dockerfile).To(Equal(`
FROM luet/base
ENV PACKAGE_NAME=a
ENV PACKAGE_VERSION=1.0
ENV PACKAGE_CATEGORY=test
ENV test=1
RUN echo foo > /test
RUN echo bar > /test2`))
})
})

403
pkg/config/config.go Normal file
View File

@@ -0,0 +1,403 @@
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package config
import (
"errors"
"fmt"
"io/ioutil"
"os"
"os/user"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/mudler/luet/pkg/helpers"
solver "github.com/mudler/luet/pkg/solver"
v "github.com/spf13/viper"
)
var LuetCfg = NewLuetConfig(v.GetViper())
var AvailableResolvers = strings.Join([]string{solver.QLearningResolverType}, " ")
type LuetLoggingConfig struct {
// Path of the logfile
Path string `mapstructure:"path"`
// Enable/Disable logging to file
EnableLogFile bool `mapstructure:"enable_logfile"`
// Enable JSON format logging in file
JsonFormat bool `mapstructure:"json_format"`
// Log level
Level string `mapstructure:"level"`
// Enable emoji
EnableEmoji bool `mapstructure:"enable_emoji"`
// Enable/Disable color in logging
Color bool `mapstructure:"color"`
}
type LuetGeneralConfig struct {
SameOwner bool `mapstructure:"same_owner"`
Concurrency int `mapstructure:"concurrency"`
Debug bool `mapstructure:"debug"`
ShowBuildOutput bool `mapstructure:"show_build_output"`
SpinnerMs int `mapstructure:"spinner_ms"`
SpinnerCharset int `mapstructure:"spinner_charset"`
FatalWarns bool `mapstructure:"fatal_warnings"`
}
type LuetSolverOptions struct {
Type string `mapstructure:"type"`
LearnRate float32 `mapstructure:"rate"`
Discount float32 `mapstructure:"discount"`
MaxAttempts int `mapstructure:"max_attempts"`
}
func (opts LuetSolverOptions) Resolver() solver.PackageResolver {
switch opts.Type {
case solver.QLearningResolverType:
if opts.LearnRate != 0.0 {
return solver.NewQLearningResolver(opts.LearnRate, opts.Discount, opts.MaxAttempts, 999999)
}
return solver.SimpleQLearningSolver()
}
return &solver.DummyPackageResolver{}
}
func (opts *LuetSolverOptions) CompactString() string {
return fmt.Sprintf("type: %s rate: %f, discount: %f, attempts: %d, initialobserved: %d",
opts.Type, opts.LearnRate, opts.Discount, opts.MaxAttempts, 999999)
}
type LuetSystemConfig struct {
DatabaseEngine string `yaml:"database_engine" mapstructure:"database_engine"`
DatabasePath string `yaml:"database_path" mapstructure:"database_path"`
Rootfs string `yaml:"rootfs" mapstructure:"rootfs"`
PkgsCachePath string `yaml:"pkgs_cache_path" mapstructure:"pkgs_cache_path"`
TmpDirBase string `yaml:"tmpdir_base" mapstructure:"tmpdir_base"`
}
func (sc LuetSystemConfig) GetRepoDatabaseDirPath(name string) string {
dbpath := filepath.Join(sc.Rootfs, sc.DatabasePath)
dbpath = filepath.Join(dbpath, "repos/"+name)
err := os.MkdirAll(dbpath, os.ModePerm)
if err != nil {
panic(err)
}
return dbpath
}
func (sc LuetSystemConfig) GetSystemRepoDatabaseDirPath() string {
dbpath := filepath.Join(sc.Rootfs,
sc.DatabasePath)
err := os.MkdirAll(dbpath, os.ModePerm)
if err != nil {
panic(err)
}
return dbpath
}
func (sc LuetSystemConfig) GetSystemPkgsCacheDirPath() (ans string) {
var cachepath string
if sc.PkgsCachePath != "" {
cachepath = sc.PkgsCachePath
} else {
// Create dynamic cache for test suites
cachepath, _ = ioutil.TempDir(os.TempDir(), "cachepkgs")
}
if filepath.IsAbs(cachepath) {
ans = cachepath
} else {
ans = filepath.Join(sc.GetSystemRepoDatabaseDirPath(), cachepath)
}
return
}
type LuetRepository struct {
Name string `json:"name" yaml:"name" mapstructure:"name"`
Description string `json:"description,omitempty" yaml:"description,omitempty" mapstructure:"description"`
Urls []string `json:"urls" yaml:"urls" mapstructure:"urls"`
Type string `json:"type" yaml:"type" mapstructure:"type"`
Mode string `json:"mode,omitempty" yaml:"mode,omitempty" mapstructure:"mode,omitempty"`
Priority int `json:"priority,omitempty" yaml:"priority,omitempty" mapstructure:"priority"`
Enable bool `json:"enable" yaml:"enable" mapstructure:"enable"`
Cached bool `json:"cached,omitempty" yaml:"cached,omitempty" mapstructure:"cached,omitempty"`
Authentication map[string]string `json:"auth,omitempty" yaml:"auth,omitempty" mapstructure:"auth,omitempty"`
TreePath string `json:"tree_path,omitempty" yaml:"tree_path,omitempty" mapstructure:"tree_path"`
MetaPath string `json:"meta_path,omitempty" yaml:"meta_path,omitempty" mapstructure:"meta_path"`
// Serialized options not used in repository configuration
// Incremented value that identify revision of the repository in a user-friendly way.
Revision int `json:"revision,omitempty" yaml:"-,omitempty" mapstructure:"-,omitempty"`
// Epoch time in seconds
LastUpdate string `json:"last_update,omitempty" yaml:"-,omitempty" mapstructure:"-,omitempty"`
}
func NewLuetRepository(name, t, descr string, urls []string, priority int, enable, cached bool) *LuetRepository {
return &LuetRepository{
Name: name,
Description: descr,
Urls: urls,
Type: t,
// Used in cached repositories
Mode: "",
Priority: priority,
Enable: enable,
Cached: cached,
Authentication: make(map[string]string, 0),
TreePath: "",
MetaPath: "",
}
}
func NewEmptyLuetRepository() *LuetRepository {
return &LuetRepository{
Name: "",
Description: "",
Urls: []string{},
Type: "",
Priority: 9999,
TreePath: "",
MetaPath: "",
Enable: false,
Cached: false,
Authentication: make(map[string]string, 0),
}
}
func (r *LuetRepository) String() string {
return fmt.Sprintf("[%s] prio: %d, type: %s, enable: %t, cached: %t",
r.Name, r.Priority, r.Type, r.Enable, r.Cached)
}
type LuetConfig struct {
Viper *v.Viper
Logging LuetLoggingConfig `mapstructure:"logging"`
General LuetGeneralConfig `mapstructure:"general"`
System LuetSystemConfig `mapstructure:"system"`
Solver LuetSolverOptions `mapstructure:"solver"`
RepositoriesConfDir []string `mapstructure:"repos_confdir"`
ConfigProtectConfDir []string `mapstructure:"config_protect_confdir"`
ConfigProtectSkip bool `mapstructure:"config_protect_skip"`
CacheRepositories []LuetRepository `mapstructure:"repetitors"`
SystemRepositories []LuetRepository `mapstructure:"repositories"`
ConfigProtectConfFiles []ConfigProtectConfFile
}
func NewLuetConfig(viper *v.Viper) *LuetConfig {
if viper == nil {
viper = v.New()
}
GenDefault(viper)
return &LuetConfig{Viper: viper, ConfigProtectConfFiles: nil}
}
func GenDefault(viper *v.Viper) {
viper.SetDefault("logging.level", "info")
viper.SetDefault("logging.enable_logfile", false)
viper.SetDefault("logging.path", "/var/log/luet.log")
viper.SetDefault("logging.json_format", false)
viper.SetDefault("logging.enable_emoji", true)
viper.SetDefault("logging.color", true)
viper.SetDefault("general.concurrency", runtime.NumCPU())
viper.SetDefault("general.debug", false)
viper.SetDefault("general.show_build_output", false)
viper.SetDefault("general.spinner_ms", 100)
viper.SetDefault("general.spinner_charset", 22)
viper.SetDefault("general.fatal_warnings", false)
u, err := user.Current()
// os/user doesn't work in from scratch environments
if err != nil || (u != nil && u.Uid == "0") {
viper.SetDefault("general.same_owner", true)
} else {
viper.SetDefault("general.same_owner", false)
}
viper.SetDefault("system.database_engine", "boltdb")
viper.SetDefault("system.database_path", "/var/cache/luet")
viper.SetDefault("system.rootfs", "/")
viper.SetDefault("system.tmpdir_base", filepath.Join(os.TempDir(), "tmpluet"))
viper.SetDefault("system.pkgs_cache_path", "packages")
viper.SetDefault("repos_confdir", []string{"/etc/luet/repos.conf.d"})
viper.SetDefault("config_protect_confdir", []string{"/etc/luet/config.protect.d"})
viper.SetDefault("config_protect_skip", false)
viper.SetDefault("cache_repositories", []string{})
viper.SetDefault("system_repositories", []string{})
viper.SetDefault("solver.type", "")
viper.SetDefault("solver.rate", 0.7)
viper.SetDefault("solver.discount", 1.0)
viper.SetDefault("solver.max_attempts", 9000)
}
func (c *LuetConfig) AddSystemRepository(r LuetRepository) {
c.SystemRepositories = append(c.SystemRepositories, r)
}
func (c *LuetConfig) GetLogging() *LuetLoggingConfig {
return &c.Logging
}
func (c *LuetConfig) GetGeneral() *LuetGeneralConfig {
return &c.General
}
func (c *LuetConfig) GetSystem() *LuetSystemConfig {
return &c.System
}
func (c *LuetConfig) GetSolverOptions() *LuetSolverOptions {
return &c.Solver
}
func (c *LuetConfig) GetConfigProtectConfFiles() []ConfigProtectConfFile {
return c.ConfigProtectConfFiles
}
func (c *LuetConfig) AddConfigProtectConfFile(file *ConfigProtectConfFile) {
if c.ConfigProtectConfFiles == nil {
c.ConfigProtectConfFiles = []ConfigProtectConfFile{*file}
} else {
c.ConfigProtectConfFiles = append(c.ConfigProtectConfFiles, *file)
}
}
func (c *LuetConfig) GetSystemRepository(name string) (*LuetRepository, error) {
var ans *LuetRepository = nil
for idx, repo := range c.SystemRepositories {
if repo.Name == name {
ans = &c.SystemRepositories[idx]
break
}
}
if ans == nil {
return nil, errors.New("Repository " + name + " not found")
}
return ans, nil
}
func (c *LuetSolverOptions) String() string {
ans := fmt.Sprintf(`
solver:
type: %s
rate: %f
discount: %f
max_attempts: %d`, c.Type, c.LearnRate, c.Discount,
c.MaxAttempts)
return ans
}
func (c *LuetGeneralConfig) String() string {
ans := fmt.Sprintf(`
general:
concurrency: %d
same_owner: %t
debug: %t
fatal_warnings: %t
show_build_output: %t
spinner_ms: %d
spinner_charset: %d`, c.Concurrency, c.SameOwner, c.Debug,
c.FatalWarns, c.ShowBuildOutput,
c.SpinnerMs, c.SpinnerCharset)
return ans
}
func (c *LuetGeneralConfig) GetSpinnerMs() time.Duration {
duration, err := time.ParseDuration(fmt.Sprintf("%dms", c.SpinnerMs))
if err != nil {
return 100 * time.Millisecond
}
return duration
}
func (c *LuetLoggingConfig) SetLogLevel(s string) {
c.Level = s
}
func (c *LuetLoggingConfig) String() string {
ans := fmt.Sprintf(`
logging:
enable_logfile: %t
path: %s
json_format: %t
color: %t
enable_emoji: %t
level: %s`, c.EnableLogFile, c.Path, c.JsonFormat,
c.Color, c.EnableEmoji, c.Level)
return ans
}
func (c *LuetSystemConfig) String() string {
ans := fmt.Sprintf(`
system:
database_engine: %s
database_path: %s
pkgs_cache_path: %s
tmpdir_base: %s
rootfs: %s`,
c.DatabaseEngine, c.DatabasePath, c.PkgsCachePath,
c.TmpDirBase, c.Rootfs)
return ans
}
func (c *LuetSystemConfig) InitTmpDir() error {
if !helpers.Exists(c.TmpDirBase) {
return os.MkdirAll(c.TmpDirBase, os.ModePerm)
}
return nil
}
func (c *LuetSystemConfig) CleanupTmpDir() error {
return os.RemoveAll(c.TmpDirBase)
}
func (c *LuetSystemConfig) TempDir(pattern string) (string, error) {
err := c.InitTmpDir()
if err != nil {
return "", err
}
return ioutil.TempDir(c.TmpDirBase, pattern)
}
func (c *LuetSystemConfig) TempFile(pattern string) (*os.File, error) {
err := c.InitTmpDir()
if err != nil {
return nil, err
}
return ioutil.TempFile(c.TmpDirBase, pattern)
}

View File

@@ -0,0 +1,41 @@
// Copyright © 2019-2020 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package config
import (
"fmt"
)
type ConfigProtectConfFile struct {
Filename string
Name string `mapstructure:"name" yaml:"name" json:"name"`
Directories []string `mapstructure:"dirs" yaml:"dirs" json:"dirs"`
}
func NewConfigProtectConfFile(filename string) *ConfigProtectConfFile {
return &ConfigProtectConfFile{
Filename: filename,
Name: "",
Directories: []string{},
}
}
func (c *ConfigProtectConfFile) String() string {
return fmt.Sprintf("[%s] filename: %s, dirs: %s", c.Name, c.Filename,
c.Directories)
}

View File

@@ -0,0 +1,33 @@
// Copyright © 2019-2020 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package config_test
import (
"testing"
. "github.com/mudler/luet/cmd"
config "github.com/mudler/luet/pkg/config"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestSolver(t *testing.T) {
RegisterFailHandler(Fail)
LoadConfig(config.LuetCfg)
RunSpecs(t, "Config Suite")
}

66
pkg/config/config_test.go Normal file
View File

@@ -0,0 +1,66 @@
// Copyright © 2019-2020 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package config_test
import (
"os"
"path/filepath"
"strings"
config "github.com/mudler/luet/pkg/config"
"github.com/mudler/luet/pkg/helpers"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Config", func() {
Context("Simple temporary directory creation", func() {
It("Create Temporary directory", func() {
// PRE: tmpdir_base contains default value.
tmpDir, err := config.LuetCfg.GetSystem().TempDir("test1")
Expect(err).ToNot(HaveOccurred())
Expect(strings.HasPrefix(tmpDir, filepath.Join(os.TempDir(), "tmpluet"))).To(BeTrue())
Expect(helpers.Exists(tmpDir)).To(BeTrue())
defer os.RemoveAll(tmpDir)
})
It("Create Temporary file", func() {
// PRE: tmpdir_base contains default value.
tmpFile, err := config.LuetCfg.GetSystem().TempFile("testfile1")
Expect(err).ToNot(HaveOccurred())
Expect(strings.HasPrefix(tmpFile.Name(), filepath.Join(os.TempDir(), "tmpluet"))).To(BeTrue())
Expect(helpers.Exists(tmpFile.Name())).To(BeTrue())
defer os.Remove(tmpFile.Name())
})
It("Config1", func() {
cfg := config.LuetCfg
cfg.GetLogging().Color = false
Expect(cfg.GetLogging().Color).To(BeFalse())
})
})
})

View File

@@ -16,8 +16,11 @@
package helpers
import (
"archive/tar"
"bytes"
"io"
"os"
"path/filepath"
"github.com/docker/docker/pkg/archive"
)
@@ -47,16 +50,171 @@ func Tar(src, dest string) error {
return err
}
// Untar just a wrapper around the docker functions
func Untar(src, dest string, sameOwner bool) error {
type TarModifierWrapperFunc func(path, dst string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error)
type TarModifierWrapper struct {
DestinationPath string
Modifier TarModifierWrapperFunc
}
func NewTarModifierWrapper(dst string, modifier TarModifierWrapperFunc) *TarModifierWrapper {
return &TarModifierWrapper{
DestinationPath: dst,
Modifier: modifier,
}
}
func (m *TarModifierWrapper) GetModifier() archive.TarModifierFunc {
return func(path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
return m.Modifier(m.DestinationPath, path, header, content)
}
}
func UntarProtect(src, dst string, sameOwner bool, protectedFiles []string, modifier *TarModifierWrapper) error {
var ans error
if len(protectedFiles) <= 0 {
return Untar(src, dst, sameOwner)
}
// POST: we have files to protect. I create a ReplaceFileTarWrapper
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
return archive.Untar(in, dest, &archive.TarOptions{
NoLchown: !sameOwner,
ExcludePatterns: []string{"dev/"}, // prevent 'operation not permitted'
})
// Create modifier map
mods := make(map[string]archive.TarModifierFunc)
for _, file := range protectedFiles {
mods[file] = modifier.GetModifier()
}
if sameOwner {
// PRE: i have root privileged.
replacerArchive := archive.ReplaceFileTarWrapper(in, mods)
opts := &archive.TarOptions{
// NOTE: NoLchown boolean is used for chmod of the symlink
// Probably it's needed set this always to true.
NoLchown: true,
ExcludePatterns: []string{"dev/"}, // prevent 'operation not permitted'
ContinueOnError: true,
}
ans = archive.Untar(replacerArchive, dst, opts)
} else {
ans = unTarIgnoreOwner(dst, in, mods)
}
return ans
}
func unTarIgnoreOwner(dest string, in io.ReadCloser, mods map[string]archive.TarModifierFunc) error {
tr := tar.NewReader(in)
for {
header, err := tr.Next()
var data []byte
var headerReplaced = false
switch {
case err == io.EOF:
goto tarEof
case err != nil:
return err
case header == nil:
continue
}
// the target location where the dir/file should be created
target := filepath.Join(dest, header.Name)
if mods != nil {
modifier, ok := mods[header.Name]
if ok {
header, data, err = modifier(header.Name, header, tr)
if err != nil {
return err
}
// Override target path
target = filepath.Join(dest, header.Name)
headerReplaced = true
}
}
// Check the file type
switch header.Typeflag {
// if its a dir and it doesn't exist create it
case tar.TypeDir:
if _, err := os.Stat(target); err != nil {
if err := os.MkdirAll(target, 0755); err != nil {
return err
}
}
// handle creation of file
case tar.TypeReg:
f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode))
if err != nil {
return err
}
// copy over contents
if headerReplaced {
_, err = io.Copy(f, bytes.NewReader(data))
} else {
_, err = io.Copy(f, tr)
}
if err != nil {
return err
}
// manually close here after each file operation; defering would cause each
// file close to wait until all operations have completed.
f.Close()
case tar.TypeSymlink:
source := header.Linkname
err := os.Symlink(source, target)
if err != nil {
return err
}
}
}
tarEof:
return nil
}
// Untar just a wrapper around the docker functions
func Untar(src, dest string, sameOwner bool) error {
var ans error
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
if sameOwner {
// PRE: i have root privileged.
opts := &archive.TarOptions{
// NOTE: NoLchown boolean is used for chmod of the symlink
// Probably it's needed set this always to true.
NoLchown: true,
ExcludePatterns: []string{"dev/"}, // prevent 'operation not permitted'
ContinueOnError: true,
}
ans = archive.Untar(in, dest, opts)
} else {
ans = unTarIgnoreOwner(dest, in, nil)
}
return ans
}

134
pkg/helpers/archive_test.go Normal file
View File

@@ -0,0 +1,134 @@
// Copyright © 2019-2020 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package helpers_test
import (
"archive/tar"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"github.com/docker/docker/pkg/archive"
. "github.com/mudler/luet/pkg/helpers"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
// Code from moby/moby pkg/archive/archive_test
func prepareUntarSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) {
fileData := []byte("fooo")
for n := 0; n < numberOfFiles; n++ {
fileName := fmt.Sprintf("file-%d", n)
if err := ioutil.WriteFile(filepath.Join(targetPath, fileName), fileData, 0700); err != nil {
return 0, err
}
if makeLinks {
if err := os.Link(filepath.Join(targetPath, fileName), filepath.Join(targetPath, fileName+"-link")); err != nil {
return 0, err
}
}
}
totalSize := numberOfFiles * len(fileData)
return totalSize, nil
}
func tarModifierWrapperFunc(dst, path string, header *tar.Header, content io.Reader) (*tar.Header, []byte, error) {
// If the destination path already exists I rename target file name with postfix.
var basePath string
// Read data. TODO: We need change archive callback to permit to return a Reader
buffer := bytes.Buffer{}
if content != nil {
if _, err := buffer.ReadFrom(content); err != nil {
return nil, nil, err
}
}
if header != nil {
switch header.Typeflag {
case tar.TypeReg:
basePath = filepath.Base(path)
default:
// Nothing to do. I return original reader
return header, buffer.Bytes(), nil
}
if basePath == "file-0" {
name := filepath.Join(filepath.Join(filepath.Dir(path), fmt.Sprintf("._cfg%04d_%s", 1, basePath)))
return &tar.Header{
Mode: header.Mode,
Typeflag: header.Typeflag,
PAXRecords: header.PAXRecords,
Name: name,
}, buffer.Bytes(), nil
} else if basePath == "file-1" {
return header, []byte("newcontent"), nil
}
// else file not present
}
return header, buffer.Bytes(), nil
}
var _ = Describe("Helpers Archive", func() {
Context("Untar Protect", func() {
It("Detect existing and not-existing files", func() {
archiveSourceDir, err := ioutil.TempDir("", "archive-source")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(archiveSourceDir)
_, err = prepareUntarSourceDirectory(10, archiveSourceDir, false)
Expect(err).ToNot(HaveOccurred())
targetDir, err := ioutil.TempDir("", "archive-target")
Expect(err).ToNot(HaveOccurred())
// defer os.RemoveAll(targetDir)
sourceArchive, err := archive.TarWithOptions(archiveSourceDir, &archive.TarOptions{})
Expect(err).ToNot(HaveOccurred())
defer sourceArchive.Close()
tarModifier := NewTarModifierWrapper(targetDir, tarModifierWrapperFunc)
mods := make(map[string]archive.TarModifierFunc)
mods["file-0"] = tarModifier.GetModifier()
mods["file-1"] = tarModifier.GetModifier()
mods["file-9999"] = tarModifier.GetModifier()
replacerArchive := archive.ReplaceFileTarWrapper(sourceArchive, mods)
//replacerArchive := archive.ReplaceFileTarWrapper(sourceArchive, mods)
opts := &archive.TarOptions{
// NOTE: NoLchown boolean is used for chmod of the symlink
// Probably it's needed set this always to true.
NoLchown: true,
ExcludePatterns: []string{"dev/"}, // prevent 'operation not permitted'
ContinueOnError: true,
}
err = archive.Untar(replacerArchive, targetDir, opts)
Expect(err).ToNot(HaveOccurred())
Expect(Exists(filepath.Join(targetDir, "._cfg0001_file-0"))).Should(Equal(true))
})
})
})

View File

@@ -19,10 +19,46 @@ import (
"io/ioutil"
"os"
"path/filepath"
"time"
copy "github.com/otiai10/copy"
)
func ListDir(dir string) ([]string, error) {
content := []string{}
err := filepath.Walk(dir,
func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
content = append(content, path)
return nil
})
return content, err
}
// Touch creates an empty file
func Touch(f string) error {
_, err := os.Stat(f)
if os.IsNotExist(err) {
file, err := os.Create(f)
if err != nil {
return err
}
defer file.Close()
} else {
currentTime := time.Now().Local()
err = os.Chtimes(f, currentTime, currentTime)
if err != nil {
return err
}
}
return nil
}
// Exists reports whether the named file or directory exists.
func Exists(name string) bool {
if _, err := os.Stat(name); err != nil {

34
pkg/helpers/helm.go Normal file
View File

@@ -0,0 +1,34 @@
package helpers
import (
"helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/engine"
"github.com/pkg/errors"
)
// RenderHelm renders the template string with helm
func RenderHelm(template string, values map[string]interface{}) (string,error) {
c := &chart.Chart{
Metadata: &chart.Metadata{
Name: "",
Version: "",
},
Templates: []*chart.File{
{Name: "templates", Data: []byte(template)},
},
Values: map[string]interface{}{"Values":values},
}
v, err := chartutil.CoalesceValues(c, map[string]interface{}{})
if err != nil {
return "",errors.Wrap(err,"while rendering template")
}
out, err := engine.Render(c, v)
if err != nil {
return "",errors.Wrap(err,"while rendering template")
}
return out["templates"],nil
}

32
pkg/helpers/helm_test.go Normal file
View File

@@ -0,0 +1,32 @@
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package helpers_test
import (
. "github.com/mudler/luet/pkg/helpers"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Helpers", func() {
Context("RenderHelm", func() {
It("Renders templates", func() {
out, err := RenderHelm("{{.Values.Test}}",map[string]interface{}{"Test":"foo"})
Expect(err).ToNot(HaveOccurred())
Expect(out).To(Equal("foo"))
})
})
})

View File

@@ -18,11 +18,15 @@ package helpers_test
import (
"testing"
. "github.com/mudler/luet/cmd"
config "github.com/mudler/luet/pkg/config"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestSolver(t *testing.T) {
RegisterFailHandler(Fail)
LoadConfig(config.LuetCfg)
RunSpecs(t, "Helpers Suite")
}

49
pkg/helpers/match.go Normal file
View File

@@ -0,0 +1,49 @@
// Copyright © 2019-2020 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package helpers
import (
"regexp"
)
func MapMatchRegex(m *map[string]string, r *regexp.Regexp) bool {
ans := false
if m != nil {
for k, v := range *m {
if r.MatchString(k + "=" + v) {
ans = true
break
}
}
}
return ans
}
func MapHasKey(m *map[string]string, label string) bool {
ans := false
if m != nil {
for k, _ := range *m {
if k == label {
ans = true
break
}
}
}
return ans
}

24
pkg/helpers/math.go Normal file
View File

@@ -0,0 +1,24 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package helpers
func Factorial(n uint64) (result uint64) {
if n > 0 {
result = n * Factorial(n-1)
return result
}
return 1
}

48
pkg/helpers/sys.go Normal file
View File

@@ -0,0 +1,48 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package helpers
import (
"os"
"os/exec"
"os/user"
"syscall"
"github.com/pkg/errors"
)
// This allows a multi-platform switch in the future
func Exec(cmd string, args []string, env []string) error {
path, err := exec.LookPath(cmd)
if err != nil {
return errors.Wrap(err, "Could not find binary in path: "+cmd)
}
return syscall.Exec(path, args, env)
}
func GetHomeDir() (ans string) {
// os/user doesn't work in from scratch environments
u, err := user.Current()
if err == nil {
ans = u.HomeDir
} else {
ans = ""
}
if os.Getenv("HOME") != "" {
ans = os.Getenv("HOME")
}
return ans
}

View File

@@ -18,11 +18,19 @@ package client_test
import (
"testing"
. "github.com/mudler/luet/cmd"
config "github.com/mudler/luet/pkg/config"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestClient(t *testing.T) {
RegisterFailHandler(Fail)
LoadConfig(config.LuetCfg)
// Set temporary directory for rootfs
config.LuetCfg.GetSystem().Rootfs = "/tmp/luet-root"
// Force dynamic path for packages cache
config.LuetCfg.GetSystem().PkgsCachePath = ""
RunSpecs(t, "Client Suite")
}

View File

@@ -16,7 +16,8 @@
package client
import (
"io/ioutil"
"fmt"
"math"
"net/url"
"os"
"path"
@@ -25,6 +26,7 @@ import (
. "github.com/mudler/luet/pkg/logger"
"github.com/mudler/luet/pkg/compiler"
"github.com/mudler/luet/pkg/config"
"github.com/mudler/luet/pkg/helpers"
"github.com/cavaliercoder/grab"
@@ -38,60 +40,151 @@ func NewHttpClient(r RepoData) *HttpClient {
return &HttpClient{RepoData: r}
}
func (c *HttpClient) PrepareReq(dst, url string) (*grab.Request, error) {
req, err := grab.NewRequest(dst, url)
if err != nil {
return nil, err
}
if val, ok := c.RepoData.Authentication["token"]; ok {
req.HTTPRequest.Header.Set("Authorization", "token "+val)
} else if val, ok := c.RepoData.Authentication["basic"]; ok {
req.HTTPRequest.Header.Set("Authorization", "Basic "+val)
}
return req, err
}
func Round(input float64) float64 {
if input < 0 {
return math.Ceil(input - 0.5)
}
return math.Floor(input + 0.5)
}
func (c *HttpClient) DownloadArtifact(artifact compiler.Artifact) (compiler.Artifact, error) {
var u *url.URL = nil
var err error
var req *grab.Request
var temp string
artifactName := path.Base(artifact.GetPath())
Info("Downloading artifact", artifactName, "from", c.RepoData.Uri)
cacheFile := filepath.Join(config.LuetCfg.GetSystem().GetSystemPkgsCacheDirPath(), artifactName)
ok := false
temp, err := ioutil.TempDir(os.TempDir(), "tree")
if err != nil {
return nil, err
// Check if file is already in cache
if helpers.Exists(cacheFile) {
Info("Use artifact", artifactName, "from cache.")
} else {
temp, err = config.LuetCfg.GetSystem().TempDir("tree")
if err != nil {
return nil, err
}
defer os.RemoveAll(temp)
client := grab.NewClient()
for _, uri := range c.RepoData.Urls {
Info("Downloading artifact", artifactName, "from", uri)
u, err = url.Parse(uri)
if err != nil {
continue
}
u.Path = path.Join(u.Path, artifactName)
req, err = c.PrepareReq(temp, u.String())
if err != nil {
continue
}
resp := client.Do(req)
if err = resp.Err(); err != nil {
continue
}
Info("Downloaded", artifactName, "of",
fmt.Sprintf("%.2f", (float64(resp.BytesComplete())/1000)/1000), "MB (",
fmt.Sprintf("%.2f", (float64(resp.BytesPerSecond())/1024)/1024), "MiB/s )")
Debug("Copying file ", filepath.Join(temp, artifactName), "to", cacheFile)
err = helpers.CopyFile(filepath.Join(temp, artifactName), cacheFile)
if err != nil {
continue
}
ok = true
break
}
if !ok {
return nil, err
}
}
file, err := ioutil.TempFile(temp, "HttpClient")
if err != nil {
return nil, err
}
u, err := url.Parse(c.RepoData.Uri)
if err != nil {
return nil, err
}
u.Path = path.Join(u.Path, artifactName)
_, err = grab.Get(temp, u.String())
if err != nil {
return nil, err
}
err = helpers.CopyFile(filepath.Join(temp, artifactName), file.Name())
return compiler.NewPackageArtifact(file.Name()), nil
newart := artifact
newart.SetPath(cacheFile)
return newart, nil
}
func (c *HttpClient) DownloadFile(name string) (string, error) {
temp, err := ioutil.TempDir(os.TempDir(), "tree")
if err != nil {
return "", err
}
file, err := ioutil.TempFile(os.TempDir(), "HttpClient")
if err != nil {
return "", err
}
//defer os.Remove(file.Name())
u, err := url.Parse(c.RepoData.Uri)
if err != nil {
return "", err
}
u.Path = path.Join(u.Path, name)
var file *os.File = nil
var u *url.URL = nil
var err error
var req *grab.Request
var temp string
Info("Downloading", u.String())
ok := false
_, err = grab.Get(temp, u.String())
temp, err = config.LuetCfg.GetSystem().TempDir("tree")
if err != nil {
return "", err
}
err = helpers.CopyFile(filepath.Join(temp, name), file.Name())
client := grab.NewClient()
for _, uri := range c.RepoData.Urls {
file, err = config.LuetCfg.GetSystem().TempFile("HttpClient")
if err != nil {
continue
}
u, err = url.Parse(uri)
if err != nil {
continue
}
u.Path = path.Join(u.Path, name)
Info("Downloading", u.String())
req, err = c.PrepareReq(temp, u.String())
if err != nil {
continue
}
resp := client.Do(req)
if err = resp.Err(); err != nil {
continue
}
Info("Downloaded", filepath.Base(resp.Filename), "of",
fmt.Sprintf("%.2f", (float64(resp.BytesComplete())/1000)/1000), "MB (",
fmt.Sprintf("%.2f", (float64(resp.BytesPerSecond())/1024)/1024), "MiB/s )")
err = helpers.CopyFile(filepath.Join(temp, name), file.Name())
if err != nil {
continue
}
ok = true
break
}
if !ok {
return "", err
}
return file.Name(), err
}

View File

@@ -44,7 +44,7 @@ var _ = Describe("Http client", func() {
err = ioutil.WriteFile(filepath.Join(tmpdir, "test.txt"), []byte(`test`), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
c := NewHttpClient(RepoData{Uri: ts.URL})
c := NewHttpClient(RepoData{Urls: []string{ts.URL}})
path, err := c.DownloadFile("test.txt")
Expect(err).ToNot(HaveOccurred())
Expect(helpers.Read(path)).To(Equal("test"))
@@ -62,7 +62,7 @@ var _ = Describe("Http client", func() {
err = ioutil.WriteFile(filepath.Join(tmpdir, "test.txt"), []byte(`test`), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
c := NewHttpClient(RepoData{Uri: ts.URL})
c := NewHttpClient(RepoData{Urls: []string{ts.URL}})
path, err := c.DownloadArtifact(&compiler.PackageArtifact{Path: "test.txt"})
Expect(err).ToNot(HaveOccurred())
Expect(helpers.Read(path.GetPath())).To(Equal("test"))

View File

@@ -16,5 +16,6 @@
package client
type RepoData struct {
Uri string
}
Urls []string
Authentication map[string]string
}

View File

@@ -16,11 +16,11 @@
package client
import (
"io/ioutil"
"os"
"path"
"path/filepath"
"github.com/mudler/luet/pkg/config"
. "github.com/mudler/luet/pkg/logger"
"github.com/mudler/luet/pkg/compiler"
@@ -36,28 +36,62 @@ func NewLocalClient(r RepoData) *LocalClient {
}
func (c *LocalClient) DownloadArtifact(artifact compiler.Artifact) (compiler.Artifact, error) {
var err error
artifactName := path.Base(artifact.GetPath())
Info("Downloading artifact", artifactName, "from", c.RepoData.Uri)
file, err := ioutil.TempFile(os.TempDir(), "localclient")
if err != nil {
return nil, err
cacheFile := filepath.Join(config.LuetCfg.GetSystem().GetSystemPkgsCacheDirPath(), artifactName)
// Check if file is already in cache
if helpers.Exists(cacheFile) {
Info("Use artifact", artifactName, "from cache.")
} else {
ok := false
for _, uri := range c.RepoData.Urls {
Info("Downloading artifact", artifactName, "from", uri)
//defer os.Remove(file.Name())
err = helpers.CopyFile(filepath.Join(uri, artifactName), cacheFile)
if err != nil {
continue
}
ok = true
break
}
if !ok {
return nil, err
}
}
//defer os.Remove(file.Name())
err = helpers.CopyFile(filepath.Join(c.RepoData.Uri, artifactName), file.Name())
return compiler.NewPackageArtifact(file.Name()), nil
newart := artifact
newart.SetPath(cacheFile)
return newart, nil
}
func (c *LocalClient) DownloadFile(name string) (string, error) {
Info("Downloading file", name, "from", c.RepoData.Uri)
var err error
var file *os.File = nil
file, err := ioutil.TempFile(os.TempDir(), "localclient")
if err != nil {
return "", err
ok := false
for _, uri := range c.RepoData.Urls {
Info("Downloading file", name, "from", uri)
file, err = config.LuetCfg.GetSystem().TempFile("localclient")
if err != nil {
continue
}
//defer os.Remove(file.Name())
err = helpers.CopyFile(filepath.Join(uri, name), file.Name())
if err != nil {
continue
}
ok = true
break
}
//defer os.Remove(file.Name())
err = helpers.CopyFile(filepath.Join(c.RepoData.Uri, name), file.Name())
if ok {
return file.Name(), nil
}
return file.Name(), err
return "", err
}

View File

@@ -39,7 +39,7 @@ var _ = Describe("Local client", func() {
err = ioutil.WriteFile(filepath.Join(tmpdir, "test.txt"), []byte(`test`), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
c := NewLocalClient(RepoData{Uri: tmpdir})
c := NewLocalClient(RepoData{Urls: []string{tmpdir}})
path, err := c.DownloadFile("test.txt")
Expect(err).ToNot(HaveOccurred())
Expect(helpers.Read(path)).To(Equal("test"))
@@ -55,7 +55,7 @@ var _ = Describe("Local client", func() {
err = ioutil.WriteFile(filepath.Join(tmpdir, "test.txt"), []byte(`test`), os.ModePerm)
Expect(err).ToNot(HaveOccurred())
c := NewLocalClient(RepoData{Uri: tmpdir})
c := NewLocalClient(RepoData{Urls: []string{tmpdir}})
path, err := c.DownloadArtifact(&compiler.PackageArtifact{Path: "test.txt"})
Expect(err).ToNot(HaveOccurred())
Expect(helpers.Read(path.GetPath())).To(Equal("test"))

View File

@@ -0,0 +1,86 @@
// Copyright © 2019-2020 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package installer
import (
"io/ioutil"
"path"
"regexp"
"github.com/ghodss/yaml"
. "github.com/mudler/luet/pkg/config"
. "github.com/mudler/luet/pkg/logger"
)
func LoadConfigProtectConfs(c *LuetConfig) error {
var regexConfs = regexp.MustCompile(`.yml$`)
for _, cdir := range c.ConfigProtectConfDir {
Debug("Parsing Config Protect Directory", cdir, "...")
files, err := ioutil.ReadDir(cdir)
if err != nil {
Debug("Skip dir", cdir, ":", err.Error())
continue
}
for _, file := range files {
if file.IsDir() {
continue
}
if !regexConfs.MatchString(file.Name()) {
Debug("File", file.Name(), "skipped.")
continue
}
content, err := ioutil.ReadFile(path.Join(cdir, file.Name()))
if err != nil {
Warning("On read file", file.Name(), ":", err.Error())
Warning("File", file.Name(), "skipped.")
continue
}
r, err := LoadConfigProtectConFile(file.Name(), content)
if err != nil {
Warning("On parse file", file.Name(), ":", err.Error())
Warning("File", file.Name(), "skipped.")
continue
}
if r.Name == "" || len(r.Directories) == 0 {
Warning("Invalid config protect file", file.Name())
Warning("File", file.Name(), "skipped.")
continue
}
c.AddConfigProtectConfFile(r)
}
}
return nil
}
func LoadConfigProtectConFile(filename string, data []byte) (*ConfigProtectConfFile, error) {
ans := NewConfigProtectConfFile(filename)
err := yaml.Unmarshal(data, &ans)
if err != nil {
return nil, err
}
return ans, nil
}

View File

@@ -0,0 +1,90 @@
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package installer
import (
"os/exec"
"github.com/ghodss/yaml"
box "github.com/mudler/luet/pkg/box"
. "github.com/mudler/luet/pkg/logger"
"github.com/pkg/errors"
)
type LuetFinalizer struct {
Shell []string `json:"shell"`
Install []string `json:"install"`
Uninstall []string `json:"uninstall"` // TODO: Where to store?
}
func (f *LuetFinalizer) RunInstall(s *System) error {
var cmd string
var args []string
if len(f.Shell) == 0 {
// Default to sh otherwise
cmd = "sh"
args = []string{"-c"}
} else {
cmd = f.Shell[0]
if len(f.Shell) > 1 {
args = f.Shell[1:]
}
}
for _, c := range f.Install {
toRun := append(args, c)
Info("Executing finalizer on ", s.Target, cmd, toRun)
if s.Target == "/" {
cmd := exec.Command(cmd, toRun...)
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
return errors.Wrap(err, "Failed running command: "+string(stdoutStderr))
}
Info(string(stdoutStderr))
} else {
b := box.NewBox(cmd, toRun, []string{}, []string{}, s.Target, false, true, true)
err := b.Run()
if err != nil {
return errors.Wrap(err, "Failed running command ")
}
}
}
return nil
}
// TODO: We don't store uninstall finalizers ?!
func (f *LuetFinalizer) RunUnInstall() error {
for _, c := range f.Uninstall {
Debug("finalizer:", "sh", "-c", c)
cmd := exec.Command("sh", "-c", c)
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
return errors.Wrap(err, "Failed running command: "+string(stdoutStderr))
}
Info(string(stdoutStderr))
}
return nil
}
func NewLuetFinalizerFromYaml(data []byte) (*LuetFinalizer, error) {
var p LuetFinalizer
err := yaml.Unmarshal(data, &p)
if err != nil {
return &p, err
}
return &p, err
}

View File

@@ -16,17 +16,16 @@
package installer
import (
"archive/tar"
"io"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
"sync"
"github.com/ghodss/yaml"
compiler "github.com/mudler/luet/pkg/compiler"
"github.com/mudler/luet/pkg/config"
"github.com/mudler/luet/pkg/helpers"
. "github.com/mudler/luet/pkg/logger"
pkg "github.com/mudler/luet/pkg/package"
@@ -36,9 +35,22 @@ import (
"github.com/pkg/errors"
)
type LuetInstallerOptions struct {
SolverOptions config.LuetSolverOptions
Concurrency int
NoDeps bool
OnlyDeps bool
Force bool
PreserveSystemEssentialData bool
FullUninstall, FullCleanUninstall bool
CheckConflicts bool
SolverUpgrade, RemoveUnavailableOnUpgrade, UpgradeNewRevisions bool
}
type LuetInstaller struct {
PackageRepositories Repositories
Concurrency int
Options LuetInstallerOptions
}
type ArtifactMatch struct {
@@ -47,154 +59,366 @@ type ArtifactMatch struct {
Repository Repository
}
type LuetFinalizer struct {
Install []string `json:"install"`
Uninstall []string `json:"uninstall"` // TODO: Where to store?
}
func (f *LuetFinalizer) RunInstall() error {
for _, c := range f.Install {
Debug("finalizer:", "sh", "-c", c)
cmd := exec.Command("sh", "-c", c)
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
return errors.Wrap(err, "Failed running command: "+string(stdoutStderr))
}
Info(string(stdoutStderr))
}
return nil
}
// TODO: We don't store uninstall finalizers ?!
func (f *LuetFinalizer) RunUnInstall() error {
for _, c := range f.Install {
Debug("finalizer:", "sh", "-c", c)
cmd := exec.Command("sh", "-c", c)
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
return errors.Wrap(err, "Failed running command: "+string(stdoutStderr))
}
Info(string(stdoutStderr))
}
return nil
}
func NewLuetFinalizerFromYaml(data []byte) (*LuetFinalizer, error) {
var p LuetFinalizer
err := yaml.Unmarshal(data, &p)
if err != nil {
return &p, err
}
return &p, err
}
func NewLuetInstaller(concurrency int) Installer {
return &LuetInstaller{Concurrency: concurrency}
func NewLuetInstaller(opts LuetInstallerOptions) Installer {
return &LuetInstaller{Options: opts}
}
func (l *LuetInstaller) Upgrade(s *System) error {
Spinner(32)
defer SpinnerStop()
syncedRepos := Repositories{}
for _, r := range l.PackageRepositories {
repo, err := r.Sync()
if err != nil {
return errors.Wrap(err, "Failed syncing repository: "+r.GetName())
}
syncedRepos = append(syncedRepos, repo)
syncedRepos, err := l.SyncRepositories(true)
if err != nil {
return err
}
// compute what to install and from where
sort.Sort(syncedRepos)
Info(":thinking: Computing upgrade, please hang tight")
// First match packages against repositories by priority
// matches := syncedRepos.PackageMatches(p)
// compute a "big" world
allRepos := pkg.NewInMemoryDatabase(false)
syncedRepos.SyncDatabase(allRepos)
solv := solver.NewSolver(s.Database, allRepos, pkg.NewInMemoryDatabase(false))
uninstall, solution, err := solv.Upgrade()
if err != nil {
return errors.Wrap(err, "Failed solving solution for upgrade")
}
// compute a "big" world
solv := solver.NewResolver(s.Database, allRepos, pkg.NewInMemoryDatabase(false), l.Options.SolverOptions.Resolver())
var uninstall pkg.Packages
var solution solver.PackagesAssertions
for _, u := range uninstall {
err := l.Uninstall(u, s)
if l.Options.SolverUpgrade {
uninstall, solution, err = solv.UpgradeUniverse(l.Options.RemoveUnavailableOnUpgrade)
if err != nil {
Warning("Failed uninstall for ", u.GetFingerPrint())
return errors.Wrap(err, "Failed solving solution for upgrade")
}
} else {
uninstall, solution, err = solv.Upgrade(!l.Options.FullUninstall, l.Options.NoDeps)
if err != nil {
return errors.Wrap(err, "Failed solving solution for upgrade")
}
}
toInstall := []pkg.Package{}
if len(uninstall) > 0 {
Info("Packages marked for uninstall:")
}
for _, p := range uninstall {
Info(fmt.Sprintf("- %s", p.HumanReadableString()))
}
if len(solution) > 0 {
Info("Packages marked for upgrade:")
}
toInstall := pkg.Packages{}
for _, assertion := range solution {
if assertion.Value {
// Be sure to filter from solutions packages already installed in the system
if _, err := s.Database.FindPackage(assertion.Package); err != nil && assertion.Value {
Info(fmt.Sprintf("- %s", assertion.Package.HumanReadableString()))
toInstall = append(toInstall, assertion.Package)
}
}
return l.Install(toInstall, s)
}
func (l *LuetInstaller) Install(p []pkg.Package, s *System) error {
// First get metas from all repos (and decodes trees)
Spinner(32)
defer SpinnerStop()
syncedRepos := Repositories{}
for _, r := range l.PackageRepositories {
repo, err := r.Sync()
if err != nil {
return errors.Wrap(err, "Failed syncing repository: "+r.GetName())
}
syncedRepos = append(syncedRepos, repo)
}
// compute what to install and from where
sort.Sort(syncedRepos)
// First match packages against repositories by priority
// matches := syncedRepos.PackageMatches(p)
// compute a "big" world
allRepos := pkg.NewInMemoryDatabase(false)
syncedRepos.SyncDatabase(allRepos)
solv := solver.NewSolver(s.Database, allRepos, pkg.NewInMemoryDatabase(false))
solution, err := solv.Install(p)
if err != nil {
return errors.Wrap(err, "Failed solving solution for package")
}
// Gathers things to install
toInstall := map[string]ArtifactMatch{}
for _, assertion := range solution {
if assertion.Value {
matches := syncedRepos.PackageMatches([]pkg.Package{assertion.Package})
if len(matches) != 1 {
return errors.New("Failed matching solutions against repository - where are definitions coming from?!")
if l.Options.UpgradeNewRevisions {
Info("Checking packages with new revisions available")
for _, p := range s.Database.World() {
matches := syncedRepos.PackageMatches(pkg.Packages{p})
if len(matches) == 0 {
// Package missing. the user should run luet upgrade --universe
Info("Installed packages seems to be missing from remote repositories.")
Info("It is suggested to run 'luet upgrade --universe'")
continue
}
A:
for _, artefact := range matches[0].Repo.GetIndex() {
if artefact.GetCompileSpec().GetPackage() == nil {
return errors.New("Package in compilespec empty")
}
if matches[0].Package.Matches(artefact.GetCompileSpec().GetPackage()) {
// Filter out already installed
if _, err := s.Database.FindPackage(assertion.Package); err != nil {
toInstall[assertion.Package.GetFingerPrint()] = ArtifactMatch{Package: assertion.Package, Artifact: artefact, Repository: matches[0].Repo}
}
break A
if artefact.GetCompileSpec().GetPackage().Matches(p) && artefact.GetCompileSpec().GetPackage().GetBuildTimestamp() != p.GetBuildTimestamp() {
toInstall = append(toInstall, matches[0].Package).Unique()
uninstall = append(uninstall, p).Unique()
Info(
fmt.Sprintf("- %s ( %s vs %s ) repo: %s (date: %s)",
p.HumanReadableString(),
artefact.GetCompileSpec().GetPackage().GetBuildTimestamp(),
p.GetBuildTimestamp(),
matches[0].Repo.GetName(),
matches[0].Repo.GetLastUpdate(),
))
}
}
}
}
return l.swap(syncedRepos, uninstall, toInstall, s)
}
func (l *LuetInstaller) SyncRepositories(inMemory bool) (Repositories, error) {
Spinner(32)
defer SpinnerStop()
syncedRepos := Repositories{}
for _, r := range l.PackageRepositories {
repo, err := r.Sync(false)
if err != nil {
return nil, errors.Wrap(err, "Failed syncing repository: "+r.GetName())
}
syncedRepos = append(syncedRepos, repo)
}
// compute what to install and from where
sort.Sort(syncedRepos)
if !inMemory {
l.PackageRepositories = syncedRepos
}
return syncedRepos, nil
}
func (l *LuetInstaller) Swap(toRemove pkg.Packages, toInstall pkg.Packages, s *System) error {
syncedRepos, err := l.SyncRepositories(true)
if err != nil {
return err
}
return l.swap(syncedRepos, toRemove, toInstall, s)
}
func (l *LuetInstaller) swap(syncedRepos Repositories, toRemove pkg.Packages, toInstall pkg.Packages, s *System) error {
// First match packages against repositories by priority
allRepos := pkg.NewInMemoryDatabase(false)
syncedRepos.SyncDatabase(allRepos)
toInstall = syncedRepos.ResolveSelectors(toInstall)
if err := l.download(syncedRepos, toInstall); err != nil {
return errors.Wrap(err, "Pre-downloading packages")
}
// We don't want any conflict with the installed to raise during the upgrade.
// In this way we both force uninstalls and we avoid to check with conflicts
// against the current system state which is pending to deletion
// E.g. you can't check for conflicts for an upgrade of a new version of A
// if the old A results installed in the system. This is due to the fact that
// now the solver enforces the constraints and explictly denies two packages
// of the same version installed.
forced := l.Options.Force
l.Options.Force = true
for _, u := range toRemove {
Info(":package:", u.HumanReadableString(), "Marked for deletion")
err := l.Uninstall(u, s)
if err != nil && !l.Options.Force {
Error("Failed uninstall for ", u.HumanReadableString())
return errors.Wrap(err, "uninstalling "+u.HumanReadableString())
}
}
l.Options.Force = forced
return l.install(syncedRepos, toInstall, s)
}
func (l *LuetInstaller) Install(cp pkg.Packages, s *System) error {
syncedRepos, err := l.SyncRepositories(true)
if err != nil {
return err
}
return l.install(syncedRepos, cp, s)
}
func (l *LuetInstaller) download(syncedRepos Repositories, cp pkg.Packages) error {
toDownload := map[string]ArtifactMatch{}
// FIXME: This can be optimized. We don't need to re-match this to the repository
// But we could just do it once
// Gathers things to download
for _, currentPack := range cp {
matches := syncedRepos.PackageMatches(pkg.Packages{currentPack})
if len(matches) == 0 {
return errors.New("Failed matching solutions against repository for " + currentPack.HumanReadableString() + " where are definitions coming from?!")
}
A:
for _, artefact := range matches[0].Repo.GetIndex() {
if artefact.GetCompileSpec().GetPackage() == nil {
return errors.New("Package in compilespec empty")
}
if matches[0].Package.Matches(artefact.GetCompileSpec().GetPackage()) {
toDownload[currentPack.GetFingerPrint()] = ArtifactMatch{Package: currentPack, Artifact: artefact, Repository: matches[0].Repo}
break A
}
}
}
// Download packages into cache in parallel.
all := make(chan ArtifactMatch)
var wg = new(sync.WaitGroup)
// Download
for i := 0; i < l.Options.Concurrency; i++ {
wg.Add(1)
go l.downloadWorker(i, wg, all)
}
for _, c := range toDownload {
all <- c
}
close(all)
wg.Wait()
return nil
}
// Reclaim adds packages to the system database
// if files from artifacts in the repositories are found
// in the system target
func (l *LuetInstaller) Reclaim(s *System) error {
syncedRepos, err := l.SyncRepositories(true)
if err != nil {
return err
}
var toMerge []ArtifactMatch = []ArtifactMatch{}
for _, repo := range syncedRepos {
for _, artefact := range repo.GetIndex() {
Debug("Checking if",
artefact.GetCompileSpec().GetPackage().HumanReadableString(),
"from", repo.GetName(), "is installed")
FILES:
for _, f := range artefact.GetFiles() {
if helpers.Exists(filepath.Join(s.Target, f)) {
p, err := repo.GetTree().GetDatabase().FindPackage(artefact.GetCompileSpec().GetPackage())
if err != nil {
return err
}
Info("Found package:", p.HumanReadableString())
toMerge = append(toMerge, ArtifactMatch{Artifact: artefact, Package: p})
break FILES
}
}
}
}
for _, match := range toMerge {
pack := match.Package
vers, _ := s.Database.FindPackageVersions(pack)
if len(vers) >= 1 {
Warning("Filtering out package " + pack.HumanReadableString() + ", already reclaimed")
continue
}
_, err := s.Database.CreatePackage(pack)
if err != nil && !l.Options.Force {
return errors.Wrap(err, "Failed creating package")
}
s.Database.SetPackageFiles(&pkg.PackageFile{PackageFingerprint: pack.GetFingerPrint(), Files: match.Artifact.GetFiles()})
Info("Reclaimed package:", pack.HumanReadableString())
}
Info("Done!")
return nil
}
func (l *LuetInstaller) install(syncedRepos Repositories, cp pkg.Packages, s *System) error {
var p pkg.Packages
// Check if the package is installed first
for _, pi := range cp {
vers, _ := s.Database.FindPackageVersions(pi)
if len(vers) >= 1 {
Warning("Filtering out package " + pi.HumanReadableString() + ", it has other versions already installed. Uninstall one of them first ")
continue
//return errors.New("Package " + pi.GetFingerPrint() + " has other versions already installed. Uninstall one of them first: " + strings.Join(vers, " "))
}
p = append(p, pi)
}
if len(p) == 0 {
Warning("No package to install, bailing out with no errors")
return nil
}
// First get metas from all repos (and decodes trees)
// First match packages against repositories by priority
// matches := syncedRepos.PackageMatches(p)
// compute a "big" world
allRepos := pkg.NewInMemoryDatabase(false)
syncedRepos.SyncDatabase(allRepos)
p = syncedRepos.ResolveSelectors(p)
toInstall := map[string]ArtifactMatch{}
var packagesToInstall pkg.Packages
var err error
var solution solver.PackagesAssertions
if !l.Options.NoDeps {
solv := solver.NewResolver(s.Database, allRepos, pkg.NewInMemoryDatabase(false), l.Options.SolverOptions.Resolver())
solution, err = solv.Install(p)
if err != nil && !l.Options.Force {
return errors.Wrap(err, "Failed solving solution for package")
}
// Gathers things to install
for _, assertion := range solution {
if assertion.Value {
packagesToInstall = append(packagesToInstall, assertion.Package)
}
}
} else if !l.Options.OnlyDeps {
for _, currentPack := range p {
packagesToInstall = append(packagesToInstall, currentPack)
}
}
Info(":deciduous_tree: Finding packages to install")
// Gathers things to install
for _, currentPack := range packagesToInstall {
// Check if package is already installed.
if _, err := s.Database.FindPackage(currentPack); err == nil {
// skip matching if it is installed already
continue
}
matches := syncedRepos.PackageMatches(pkg.Packages{currentPack})
if len(matches) == 0 {
return errors.New("Failed matching solutions against repository for " + currentPack.HumanReadableString() + " where are definitions coming from?!")
}
A:
for _, artefact := range matches[0].Repo.GetIndex() {
if artefact.GetCompileSpec().GetPackage() == nil {
return errors.New("Package in compilespec empty")
}
if matches[0].Package.Matches(artefact.GetCompileSpec().GetPackage()) {
currentPack.SetBuildTimestamp(artefact.GetCompileSpec().GetPackage().GetBuildTimestamp())
// Filter out already installed
if _, err := s.Database.FindPackage(currentPack); err != nil {
toInstall[currentPack.GetFingerPrint()] = ArtifactMatch{Package: currentPack, Artifact: artefact, Repository: matches[0].Repo}
Info("\t:package:", currentPack.HumanReadableString(), "from repository", matches[0].Repo.GetName())
}
break A
}
}
}
// Install packages into rootfs in parallel.
all := make(chan ArtifactMatch)
var wg = new(sync.WaitGroup)
for i := 0; i < l.Concurrency; i++ {
// Download first
for i := 0; i < l.Options.Concurrency; i++ {
wg.Add(1)
go l.downloadWorker(i, wg, all)
}
for _, c := range toInstall {
all <- c
}
close(all)
wg.Wait()
all = make(chan ArtifactMatch)
wg = new(sync.WaitGroup)
// Do the real install
for i := 0; i < l.Options.Concurrency; i++ {
wg.Add(1)
go l.installerWorker(i, wg, all, s)
}
@@ -205,99 +429,119 @@ func (l *LuetInstaller) Install(p []pkg.Package, s *System) error {
close(all)
wg.Wait()
for _, c := range toInstall {
// Annotate to the system that the package was installed
_, err := s.Database.CreatePackage(c.Package)
if err != nil && !l.Options.Force {
return errors.Wrap(err, "Failed creating package")
}
}
executedFinalizer := map[string]bool{}
// TODO: Lower those errors as warning
for _, w := range p {
// Finalizers needs to run in order and in sequence.
ordered := solution.Order(allRepos, w.GetFingerPrint())
for _, ass := range ordered {
if ass.Value {
// Annotate to the system that the package was installed
// TODO: Annotate also files that belong to the package, somewhere to uninstall
if _, err := s.Database.FindPackage(ass.Package); err == nil {
err := s.Database.UpdatePackage(ass.Package)
if err != nil {
return errors.Wrap(err, "Failed updating package")
}
} else {
_, err := s.Database.CreatePackage(ass.Package)
if err != nil {
return errors.Wrap(err, "Failed creating package")
}
}
installed, ok := toInstall[ass.Package.GetFingerPrint()]
if !ok {
return errors.New("Couldn't find ArtifactMatch for " + ass.Package.GetFingerPrint())
}
if !l.Options.NoDeps {
// TODO: Lower those errors as warning
for _, w := range p {
// Finalizers needs to run in order and in sequence.
ordered, err := solution.Order(allRepos, w.GetFingerPrint())
if err != nil {
return errors.Wrap(err, "While order a solution for "+w.HumanReadableString())
}
ORDER:
for _, ass := range ordered {
if ass.Value {
treePackage, err := installed.Repository.GetTree().GetDatabase().FindPackage(ass.Package)
if err != nil {
return errors.Wrap(err, "Error getting package "+ass.Package.GetFingerPrint())
}
if helpers.Exists(treePackage.Rel(tree.FinalizerFile)) {
Info("Executing finalizer for " + ass.Package.GetName())
finalizerRaw, err := ioutil.ReadFile(treePackage.Rel(tree.FinalizerFile))
installed, ok := toInstall[ass.Package.GetFingerPrint()]
if !ok {
// It was a dep already installed in the system, so we can skip it safely
continue ORDER
}
treePackage, err := installed.Repository.GetTree().GetDatabase().FindPackage(ass.Package)
if err != nil {
return errors.Wrap(err, "Error reading file "+treePackage.Rel(tree.FinalizerFile))
return errors.Wrap(err, "Error getting package "+ass.Package.HumanReadableString())
}
if _, exists := executedFinalizer[ass.Package.GetFingerPrint()]; !exists {
finalizer, err := NewLuetFinalizerFromYaml(finalizerRaw)
if err != nil {
return errors.Wrap(err, "Error reading finalizer "+treePackage.Rel(tree.FinalizerFile))
if helpers.Exists(treePackage.Rel(tree.FinalizerFile)) {
Info("Executing finalizer for " + ass.Package.HumanReadableString())
finalizerRaw, err := ioutil.ReadFile(treePackage.Rel(tree.FinalizerFile))
if err != nil && !l.Options.Force {
return errors.Wrap(err, "Error reading file "+treePackage.Rel(tree.FinalizerFile))
}
err = finalizer.RunInstall()
if err != nil {
return errors.Wrap(err, "Error executing install finalizer "+treePackage.Rel(tree.FinalizerFile))
if _, exists := executedFinalizer[ass.Package.GetFingerPrint()]; !exists {
finalizer, err := NewLuetFinalizerFromYaml(finalizerRaw)
if err != nil && !l.Options.Force {
return errors.Wrap(err, "Error reading finalizer "+treePackage.Rel(tree.FinalizerFile))
}
err = finalizer.RunInstall(s)
if err != nil && !l.Options.Force {
return errors.Wrap(err, "Error executing install finalizer "+treePackage.Rel(tree.FinalizerFile))
}
executedFinalizer[ass.Package.GetFingerPrint()] = true
}
executedFinalizer[ass.Package.GetFingerPrint()] = true
}
}
}
}
} else {
for _, c := range toInstall {
treePackage, err := c.Repository.GetTree().GetDatabase().FindPackage(c.Package)
if err != nil {
return errors.Wrap(err, "Error getting package "+c.Package.HumanReadableString())
}
if helpers.Exists(treePackage.Rel(tree.FinalizerFile)) {
Info("Executing finalizer for " + c.Package.HumanReadableString())
finalizerRaw, err := ioutil.ReadFile(treePackage.Rel(tree.FinalizerFile))
if err != nil && !l.Options.Force {
return errors.Wrap(err, "Error reading file "+treePackage.Rel(tree.FinalizerFile))
}
if _, exists := executedFinalizer[c.Package.GetFingerPrint()]; !exists {
finalizer, err := NewLuetFinalizerFromYaml(finalizerRaw)
if err != nil && !l.Options.Force {
return errors.Wrap(err, "Error reading finalizer "+treePackage.Rel(tree.FinalizerFile))
}
err = finalizer.RunInstall(s)
if err != nil && !l.Options.Force {
return errors.Wrap(err, "Error executing install finalizer "+treePackage.Rel(tree.FinalizerFile))
}
executedFinalizer[c.Package.GetFingerPrint()] = true
}
}
}
}
return nil
}
func (l *LuetInstaller) downloadPackage(a ArtifactMatch) (compiler.Artifact, error) {
artifact, err := a.Repository.Client().DownloadArtifact(a.Artifact)
if err != nil {
return nil, errors.Wrap(err, "Error on download artifact")
}
err = artifact.Verify()
if err != nil && !l.Options.Force {
return nil, errors.Wrap(err, "Artifact integrity check failure")
}
return artifact, nil
}
func (l *LuetInstaller) installPackage(a ArtifactMatch, s *System) error {
// FIXME: Implement
artifact, err := a.Repository.Client().DownloadArtifact(a.Artifact)
defer os.Remove(artifact.GetPath())
artifact, err := l.downloadPackage(a)
if err != nil && !l.Options.Force {
return errors.Wrap(err, "Failed downloading package")
}
tarFile, err := os.Open(artifact.GetPath())
if err != nil {
files, err := artifact.FileList()
if err != nil && !l.Options.Force {
return errors.Wrap(err, "Could not open package archive")
}
defer tarFile.Close()
tr := tar.NewReader(tarFile)
var files []string
// untar each segment
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
// determine proper file path info
finfo := hdr.FileInfo()
fileName := hdr.Name
if finfo.Mode().IsDir() {
continue
}
files = append(files, fileName)
// if a dir, create it, then go to next segment
}
err = helpers.Untar(artifact.GetPath(), s.Target, true)
if err != nil {
err = artifact.Unpack(s.Target, true)
if err != nil && !l.Options.Force {
return errors.Wrap(err, "Error met while unpacking rootfs")
}
@@ -306,17 +550,43 @@ func (l *LuetInstaller) installPackage(a ArtifactMatch, s *System) error {
return s.Database.SetPackageFiles(&pkg.PackageFile{PackageFingerprint: a.Package.GetFingerPrint(), Files: files})
}
func (l *LuetInstaller) downloadWorker(i int, wg *sync.WaitGroup, c <-chan ArtifactMatch) error {
defer wg.Done()
for p := range c {
// TODO: Keep trace of what was added from the tar, and save it into system
_, err := l.downloadPackage(p)
if err != nil && !l.Options.Force {
//TODO: Uninstall, rollback.
Fatal("Failed installing package "+p.Package.GetName(), err.Error())
return errors.Wrap(err, "Failed installing package "+p.Package.GetName())
}
if err == nil {
Info(":package: ", p.Package.HumanReadableString(), "downloaded")
} else if err != nil && l.Options.Force {
Info(":package: ", p.Package.HumanReadableString(), "downloaded with failures (force download)")
}
}
return nil
}
func (l *LuetInstaller) installerWorker(i int, wg *sync.WaitGroup, c <-chan ArtifactMatch, s *System) error {
defer wg.Done()
for p := range c {
// TODO: Keep trace of what was added from the tar, and save it into system
err := l.installPackage(p, s)
if err != nil {
if err != nil && !l.Options.Force {
//TODO: Uninstall, rollback.
Fatal("Failed installing package "+p.Package.GetName(), err.Error())
return errors.Wrap(err, "Failed installing package "+p.Package.GetName())
}
if err == nil {
Info(":package: ", p.Package.HumanReadableString(), "installed")
} else if err != nil && l.Options.Force {
Info(":package: ", p.Package.HumanReadableString(), "installed with failures (force install)")
}
}
return nil
@@ -331,7 +601,15 @@ func (l *LuetInstaller) uninstall(p pkg.Package, s *System) error {
// Remove from target
for _, f := range files {
target := filepath.Join(s.Target, f)
Info("Removing", target)
Debug("Removing", target)
if l.Options.PreserveSystemEssentialData &&
strings.HasPrefix(f, config.LuetCfg.GetSystem().GetSystemPkgsCacheDirPath()) ||
strings.HasPrefix(f, config.LuetCfg.GetSystem().GetSystemRepoDatabaseDirPath()) {
Warning("Preserve ", f, " which is required by luet ( you have to delete it manually if you really need to)")
continue
}
err := os.Remove(target)
if err != nil {
Warning("Failed removing file (not present in the system target ?)", target)
@@ -351,20 +629,62 @@ func (l *LuetInstaller) uninstall(p pkg.Package, s *System) error {
}
func (l *LuetInstaller) Uninstall(p pkg.Package, s *System) error {
// compute uninstall from all world - remove packages in parallel - run uninstall finalizer (in order) - mark the uninstallation in db
// Get installed definition
Spinner(32)
defer SpinnerStop()
solv := solver.NewSolver(s.Database, s.Database, pkg.NewInMemoryDatabase(false))
solution, err := solv.Uninstall(p)
if err != nil {
return errors.Wrap(err, "Uninstall failed")
Info("Uninstalling :package:", p.HumanReadableString(), "hang tight")
// compute uninstall from all world - remove packages in parallel - run uninstall finalizer (in order) TODO - mark the uninstallation in db
// Get installed definition
checkConflicts := l.Options.CheckConflicts
full := l.Options.FullUninstall
if l.Options.Force == true { // IF forced, we want to remove the package and all its requires
checkConflicts = false
full = false
}
for _, p := range solution {
Info("Uninstalling", p.GetFingerPrint())
err := l.uninstall(p, s)
// Create a temporary DB with the installed packages
// so the solver is much faster finding the deptree
installedtmp := pkg.NewInMemoryDatabase(false)
for _, i := range s.Database.World() {
_, err := installedtmp.CreatePackage(i)
if err != nil {
return errors.Wrap(err, "Failed create temporary in-memory db")
}
}
if !l.Options.NoDeps {
Info("Finding :package:", p.HumanReadableString(), "dependency graph :deciduous_tree:")
solv := solver.NewResolver(installedtmp, installedtmp, pkg.NewInMemoryDatabase(false), l.Options.SolverOptions.Resolver())
var solution pkg.Packages
var err error
if l.Options.FullCleanUninstall {
solution, err = solv.UninstallUniverse(pkg.Packages{p})
if err != nil {
return errors.Wrap(err, "Could not solve the uninstall constraints. Tip: try with --solver-type qlearning or with --force, or by removing packages excluding their dependencies with --nodeps")
}
} else {
solution, err = solv.Uninstall(p, checkConflicts, full)
if err != nil && !l.Options.Force {
return errors.Wrap(err, "Could not solve the uninstall constraints. Tip: try with --solver-type qlearning or with --force, or by removing packages excluding their dependencies with --nodeps")
}
}
for _, p := range solution {
Info("Uninstalling", p.HumanReadableString())
err := l.uninstall(p, s)
if err != nil && !l.Options.Force {
return errors.Wrap(err, "Uninstall failed")
}
}
} else {
Info("Uninstalling", p.HumanReadableString(), "without deps")
err := l.uninstall(p, s)
if err != nil && !l.Options.Force {
return errors.Wrap(err, "Uninstall failed")
}
Info(":package:", p.HumanReadableString(), "uninstalled")
}
return nil

View File

@@ -18,11 +18,19 @@ package installer_test
import (
"testing"
. "github.com/mudler/luet/cmd"
config "github.com/mudler/luet/pkg/config"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestInstaller(t *testing.T) {
RegisterFailHandler(Fail)
LoadConfig(config.LuetCfg)
// Set temporary directory for rootfs
config.LuetCfg.GetSystem().Rootfs = "/tmp/luet-root"
// Force dynamic path for packages cache
config.LuetCfg.GetSystem().PkgsCachePath = ""
RunSpecs(t, "Installer Suite")
}

File diff suppressed because it is too large Load Diff

View File

@@ -23,10 +23,14 @@ import (
)
type Installer interface {
Install([]pkg.Package, *System) error
Install(pkg.Packages, *System) error
Uninstall(pkg.Package, *System) error
Upgrade(s *System) error
Reclaim(s *System) error
Repositories([]Repository)
SyncRepositories(bool) (Repositories, error)
Swap(pkg.Packages, pkg.Packages, *System) error
}
type Client interface {
@@ -38,17 +42,34 @@ type Repositories []Repository
type Repository interface {
GetName() string
GetUri() string
SetUri(string)
GetDescription() string
GetUrls() []string
SetUrls([]string)
AddUrl(string)
GetPriority() int
GetIndex() compiler.ArtifactIndex
SetIndex(i compiler.ArtifactIndex)
GetTree() tree.Builder
SetTree(tree.Builder)
Write(path string) error
Sync() (Repository, error)
Write(path string, resetRevision bool) error
Sync(bool) (Repository, error)
GetTreePath() string
SetTreePath(string)
GetMetaPath() string
SetMetaPath(string)
GetType() string
SetType(string)
SetAuthentication(map[string]string)
GetAuthentication() map[string]string
GetRevision() int
IncrementRevision()
GetLastUpdate() string
SetLastUpdate(string)
Client() Client
SetPriority(int)
GetRepositoryFile(string) (LuetRepositoryFile, error)
SetRepositoryFile(string, LuetRepositoryFile)
SetName(p string)
Serialize() (*LuetSystemRepositoryMetadata, LuetSystemRepositorySerialized)
}

View File

@@ -16,82 +16,234 @@
package installer
import (
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/mudler/luet/pkg/installer/client"
"github.com/ghodss/yaml"
"github.com/mudler/luet/pkg/compiler"
"github.com/mudler/luet/pkg/config"
"github.com/mudler/luet/pkg/helpers"
"github.com/mudler/luet/pkg/installer/client"
. "github.com/mudler/luet/pkg/logger"
pkg "github.com/mudler/luet/pkg/package"
tree "github.com/mudler/luet/pkg/tree"
"github.com/ghodss/yaml"
"github.com/pkg/errors"
)
type LuetRepository struct {
Name string `json:"name"`
Uri string `json:"uri"`
Priority int `json:"priority"`
Index compiler.ArtifactIndex `json:"index"`
Tree tree.Builder `json:"-"`
TreePath string `json:"-"`
Type string `json:"type"`
const (
REPOSITORY_METAFILE = "repository.meta.yaml"
REPOSITORY_SPECFILE = "repository.yaml"
TREE_TARBALL = "tree.tar"
REPOFILE_TREE_KEY = "tree"
REPOFILE_META_KEY = "meta"
)
type LuetRepositoryFile struct {
FileName string `json:"filename"`
CompressionType compiler.CompressionImplementation `json:"compressiontype,omitempty"`
Checksums compiler.Checksums `json:"checksums,omitempty"`
}
type LuetRepositorySerialized struct {
Name string `json:"name"`
Uri string `json:"uri"`
Priority int `json:"priority"`
Index []*compiler.PackageArtifact `json:"index"`
Type string `json:"type"`
type LuetSystemRepository struct {
*config.LuetRepository
Index compiler.ArtifactIndex `json:"index"`
Tree tree.Builder `json:"-"`
RepositoryFiles map[string]LuetRepositoryFile `json:"repo_files"`
}
func GenerateRepository(name, uri, t string, priority int, src, treeDir string, db pkg.PackageDatabase) (Repository, error) {
type LuetSystemRepositorySerialized struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Urls []string `json:"urls"`
Priority int `json:"priority"`
Type string `json:"type"`
Revision int `json:"revision,omitempty"`
LastUpdate string `json:"last_update,omitempty"`
TreePath string `json:"treepath"`
MetaPath string `json:"metapath"`
RepositoryFiles map[string]LuetRepositoryFile `json:"repo_files"`
}
art, err := buildPackageIndex(src)
type LuetSystemRepositoryMetadata struct {
Index []*compiler.PackageArtifact `json:"index,omitempty"`
}
type LuetSearchModeType string
const (
SLabel LuetSearchModeType = "label"
SRegexPkg LuetSearchModeType = "regexPkg"
SRegexLabel LuetSearchModeType = "regexLabel"
)
type LuetSearchOpts struct {
Pattern string
Mode LuetSearchModeType
}
func NewLuetSystemRepositoryMetadata(file string, removeFile bool) (*LuetSystemRepositoryMetadata, error) {
ans := &LuetSystemRepositoryMetadata{}
err := ans.ReadFile(file, removeFile)
if err != nil {
return nil, err
}
return ans, nil
}
func (m *LuetSystemRepositoryMetadata) WriteFile(path string) error {
data, err := yaml.Marshal(m)
if err != nil {
return err
}
err = ioutil.WriteFile(path, data, os.ModePerm)
if err != nil {
return err
}
return nil
}
func (m *LuetSystemRepositoryMetadata) ReadFile(file string, removeFile bool) error {
if file == "" {
return errors.New("Invalid path for repository metadata")
}
dat, err := ioutil.ReadFile(file)
if err != nil {
return err
}
if removeFile {
defer os.Remove(file)
}
err = yaml.Unmarshal(dat, m)
if err != nil {
return err
}
return nil
}
func (m *LuetSystemRepositoryMetadata) ToArtifactIndex() (ans compiler.ArtifactIndex) {
for _, a := range m.Index {
ans = append(ans, a)
}
return
}
func NewDefaultTreeRepositoryFile() LuetRepositoryFile {
return LuetRepositoryFile{
FileName: TREE_TARBALL,
CompressionType: compiler.GZip,
}
}
func NewDefaultMetaRepositoryFile() LuetRepositoryFile {
return LuetRepositoryFile{
FileName: REPOSITORY_METAFILE + ".tar",
CompressionType: compiler.None,
}
}
func (f *LuetRepositoryFile) SetFileName(n string) {
f.FileName = n
}
func (f *LuetRepositoryFile) GetFileName() string {
return f.FileName
}
func (f *LuetRepositoryFile) SetCompressionType(c compiler.CompressionImplementation) {
f.CompressionType = c
}
func (f *LuetRepositoryFile) GetCompressionType() compiler.CompressionImplementation {
return f.CompressionType
}
func (f *LuetRepositoryFile) SetChecksums(c compiler.Checksums) {
f.Checksums = c
}
func (f *LuetRepositoryFile) GetChecksums() compiler.Checksums {
return f.Checksums
}
func GenerateRepository(name, descr, t string, urls []string, priority int, src string, treesDir []string, db pkg.PackageDatabase) (Repository, error) {
tr := tree.NewInstallerRecipe(db)
err = tr.Load(treeDir)
for _, treeDir := range treesDir {
err := tr.Load(treeDir)
if err != nil {
return nil, err
}
}
art, err := buildPackageIndex(src, tr.GetDatabase())
if err != nil {
return nil, err
}
return NewLuetRepository(name, uri, t, priority, art, tr), nil
return NewLuetSystemRepository(
config.NewLuetRepository(name, t, descr, urls, priority, true, false),
art, tr), nil
}
func NewLuetRepository(name, uri, t string, priority int, art []compiler.Artifact, builder tree.Builder) Repository {
return &LuetRepository{Index: art, Type: t, Tree: builder, Name: name, Uri: uri, Priority: priority}
func NewSystemRepository(repo config.LuetRepository) Repository {
return &LuetSystemRepository{
LuetRepository: &repo,
RepositoryFiles: map[string]LuetRepositoryFile{},
}
}
func NewLuetRepositoryFromYaml(data []byte, db pkg.PackageDatabase) (Repository, error) {
var p *LuetRepositorySerialized
r := &LuetRepository{}
func NewLuetSystemRepository(repo *config.LuetRepository, art []compiler.Artifact, builder tree.Builder) Repository {
return &LuetSystemRepository{
LuetRepository: repo,
Index: art,
Tree: builder,
RepositoryFiles: map[string]LuetRepositoryFile{},
}
}
func NewLuetSystemRepositoryFromYaml(data []byte, db pkg.PackageDatabase) (Repository, error) {
var p *LuetSystemRepositorySerialized
err := yaml.Unmarshal(data, &p)
if err != nil {
return nil, err
}
r.Name = p.Name
r.Uri = p.Uri
r.Priority = p.Priority
r.Type = p.Type
i := compiler.ArtifactIndex{}
for _, ii := range p.Index {
i = append(i, ii)
r := &LuetSystemRepository{
LuetRepository: config.NewLuetRepository(
p.Name,
p.Type,
p.Description,
p.Urls,
p.Priority,
true,
false,
),
RepositoryFiles: p.RepositoryFiles,
}
if p.Revision > 0 {
r.Revision = p.Revision
}
if p.LastUpdate != "" {
r.LastUpdate = p.LastUpdate
}
r.Index = i
r.Tree = tree.NewInstallerRecipe(db)
return r, err
}
func buildPackageIndex(path string) ([]compiler.Artifact, error) {
func buildPackageIndex(path string, db pkg.PackageDatabase) ([]compiler.Artifact, error) {
var art []compiler.Artifact
var ff = func(currentpath string, info os.FileInfo, err error) error {
@@ -109,6 +261,13 @@ func buildPackageIndex(path string) ([]compiler.Artifact, error) {
if err != nil {
return errors.Wrap(err, "Error reading yaml "+currentpath)
}
// We want to include packages that are ONLY referenced in the tree.
// the ones which aren't should be deleted. (TODO: by another cli command?)
if _, notfound := db.FindPackage(artifact.GetCompileSpec().GetPackage()); notfound != nil {
return nil
}
art = append(art, artifact)
return nil
@@ -122,57 +281,151 @@ func buildPackageIndex(path string) ([]compiler.Artifact, error) {
return art, nil
}
func (r *LuetRepository) GetName() string {
return r.Name
func (r *LuetSystemRepository) SetPriority(n int) {
r.LuetRepository.Priority = n
}
func (r *LuetRepository) GetTreePath() string {
func (r *LuetSystemRepository) GetName() string {
return r.LuetRepository.Name
}
func (r *LuetSystemRepository) GetDescription() string {
return r.LuetRepository.Description
}
func (r *LuetSystemRepository) GetAuthentication() map[string]string {
return r.LuetRepository.Authentication
}
func (r *LuetSystemRepository) GetType() string {
return r.LuetRepository.Type
}
func (r *LuetSystemRepository) SetType(p string) {
r.LuetRepository.Type = p
}
func (r *LuetSystemRepository) SetName(p string) {
r.LuetRepository.Name = p
}
func (r *LuetSystemRepository) AddUrl(p string) {
r.LuetRepository.Urls = append(r.LuetRepository.Urls, p)
}
func (r *LuetSystemRepository) GetUrls() []string {
return r.LuetRepository.Urls
}
func (r *LuetSystemRepository) SetUrls(urls []string) {
r.LuetRepository.Urls = urls
}
func (r *LuetSystemRepository) GetPriority() int {
return r.LuetRepository.Priority
}
func (r *LuetSystemRepository) GetTreePath() string {
return r.TreePath
}
func (r *LuetRepository) SetTreePath(p string) {
func (r *LuetSystemRepository) SetTreePath(p string) {
r.TreePath = p
}
func (r *LuetRepository) SetTree(b tree.Builder) {
func (r *LuetSystemRepository) GetMetaPath() string {
return r.MetaPath
}
func (r *LuetSystemRepository) SetMetaPath(p string) {
r.MetaPath = p
}
func (r *LuetSystemRepository) SetTree(b tree.Builder) {
r.Tree = b
}
func (r *LuetRepository) GetType() string {
return r.Type
}
func (r *LuetRepository) SetType(p string) {
r.Type = p
}
func (r *LuetRepository) SetUri(p string) {
r.Uri = p
}
func (r *LuetRepository) GetUri() string {
return r.Uri
}
func (r *LuetRepository) GetPriority() int {
return r.Priority
}
func (r *LuetRepository) GetIndex() compiler.ArtifactIndex {
func (r *LuetSystemRepository) GetIndex() compiler.ArtifactIndex {
return r.Index
}
func (r *LuetRepository) GetTree() tree.Builder {
func (r *LuetSystemRepository) SetIndex(i compiler.ArtifactIndex) {
r.Index = i
}
func (r *LuetSystemRepository) GetTree() tree.Builder {
return r.Tree
}
func (r *LuetSystemRepository) GetRevision() int {
return r.LuetRepository.Revision
}
func (r *LuetSystemRepository) GetLastUpdate() string {
return r.LuetRepository.LastUpdate
}
func (r *LuetSystemRepository) SetLastUpdate(u string) {
r.LuetRepository.LastUpdate = u
}
func (r *LuetSystemRepository) IncrementRevision() {
r.LuetRepository.Revision++
}
func (r *LuetSystemRepository) SetAuthentication(auth map[string]string) {
r.LuetRepository.Authentication = auth
}
func (r *LuetSystemRepository) GetRepositoryFile(name string) (LuetRepositoryFile, error) {
ans, ok := r.RepositoryFiles[name]
if ok {
return ans, nil
}
return ans, errors.New("Repository file " + name + " not found!")
}
func (r *LuetSystemRepository) SetRepositoryFile(name string, f LuetRepositoryFile) {
r.RepositoryFiles[name] = f
}
func (r *LuetRepository) Write(dst string) error {
func (r *LuetSystemRepository) ReadSpecFile(file string, removeFile bool) (Repository, error) {
dat, err := ioutil.ReadFile(file)
if err != nil {
return nil, errors.Wrap(err, "Error reading file "+file)
}
if removeFile {
defer os.Remove(file)
}
os.MkdirAll(dst, os.ModePerm)
r.Index = r.Index.CleanPath()
var repo Repository
repo, err = NewLuetSystemRepositoryFromYaml(dat, pkg.NewInMemoryDatabase(false))
if err != nil {
return nil, errors.Wrap(err, "Error reading repository from file "+file)
}
data, err := yaml.Marshal(r)
// Check if mandatory key are present
_, err = repo.GetRepositoryFile(REPOFILE_TREE_KEY)
if err != nil {
return nil, errors.New("Invalid repository without the " + REPOFILE_TREE_KEY + " key file.")
}
_, err = repo.GetRepositoryFile(REPOFILE_META_KEY)
if err != nil {
return nil, errors.New("Invalid repository without the " + REPOFILE_META_KEY + " key file.")
}
return repo, err
}
func (r *LuetSystemRepository) Write(dst string, resetRevision bool) error {
err := os.MkdirAll(dst, os.ModePerm)
if err != nil {
return err
}
err = ioutil.WriteFile(filepath.Join(dst, "repository.yaml"), data, os.ModePerm)
if err != nil {
return err
r.LastUpdate = strconv.FormatInt(time.Now().Unix(), 10)
repospec := filepath.Join(dst, REPOSITORY_SPECFILE)
if resetRevision {
r.Revision = 0
} else {
if _, err := os.Stat(repospec); !os.IsNotExist(err) {
// Read existing file for retrieve revision
spec, err := r.ReadSpecFile(repospec, false)
if err != nil {
return err
}
r.Revision = spec.GetRevision()
}
}
archive, err := ioutil.TempDir(os.TempDir(), "archive")
r.Revision++
Info(fmt.Sprintf(
"For repository %s creating revision %d and last update %s...",
r.Name, r.Revision, r.LastUpdate,
))
// Create tree and repository file
archive, err := config.LuetCfg.GetSystem().TempDir("archive")
if err != nil {
return errors.Wrap(err, "Error met while creating tempdir for archive")
}
@@ -181,90 +434,309 @@ func (r *LuetRepository) Write(dst string) error {
if err != nil {
return errors.Wrap(err, "Error met while saving the tree")
}
err = helpers.Tar(archive, filepath.Join(dst, "tree.tar"))
treeFile, err := r.GetRepositoryFile(REPOFILE_TREE_KEY)
if err != nil {
treeFile = NewDefaultTreeRepositoryFile()
r.SetRepositoryFile(REPOFILE_TREE_KEY, treeFile)
}
a := compiler.NewPackageArtifact(filepath.Join(dst, treeFile.GetFileName()))
a.SetCompressionType(treeFile.GetCompressionType())
err = a.Compress(archive, 1)
if err != nil {
return errors.Wrap(err, "Error met while creating package archive")
}
return nil
}
func (r *LuetRepository) Client() Client {
switch r.GetType() {
case "local":
return client.NewLocalClient(client.RepoData{Uri: r.GetUri()})
case "http":
return client.NewHttpClient(client.RepoData{Uri: r.GetUri()})
// Update the tree name with the name created by compression selected.
treeFile.SetFileName(path.Base(a.GetPath()))
err = a.Hash()
if err != nil {
return errors.Wrap(err, "Failed generating checksums for tree")
}
treeFile.SetChecksums(a.GetChecksums())
r.SetRepositoryFile(REPOFILE_TREE_KEY, treeFile)
// Create Metadata struct and serialized repository
meta, serialized := r.Serialize()
// Create metadata file and repository file
metaTmpDir, err := config.LuetCfg.GetSystem().TempDir("metadata")
defer os.RemoveAll(metaTmpDir) // clean up
if err != nil {
return errors.Wrap(err, "Error met while creating tempdir for metadata")
}
metaFile, err := r.GetRepositoryFile(REPOFILE_META_KEY)
if err != nil {
metaFile = NewDefaultMetaRepositoryFile()
r.SetRepositoryFile(REPOFILE_META_KEY, metaFile)
}
repoMetaSpec := filepath.Join(metaTmpDir, REPOSITORY_METAFILE)
// Create repository.meta.yaml file
err = meta.WriteFile(repoMetaSpec)
if err != nil {
return err
}
a = compiler.NewPackageArtifact(filepath.Join(dst, metaFile.GetFileName()))
a.SetCompressionType(metaFile.GetCompressionType())
err = a.Compress(metaTmpDir, 1)
if err != nil {
return errors.Wrap(err, "Error met while archiving repository metadata")
}
metaFile.SetFileName(path.Base(a.GetPath()))
r.SetRepositoryFile(REPOFILE_META_KEY, metaFile)
err = a.Hash()
if err != nil {
return errors.Wrap(err, "Failed generating checksums for metadata")
}
metaFile.SetChecksums(a.GetChecksums())
data, err := yaml.Marshal(serialized)
if err != nil {
return err
}
err = ioutil.WriteFile(repospec, data, os.ModePerm)
if err != nil {
return err
}
return nil
}
func (r *LuetRepository) Sync() (Repository, error) {
func (r *LuetSystemRepository) Client() Client {
switch r.GetType() {
case "disk":
return client.NewLocalClient(client.RepoData{Urls: r.GetUrls()})
case "http":
return client.NewHttpClient(
client.RepoData{
Urls: r.GetUrls(),
Authentication: r.GetAuthentication(),
})
}
return nil
}
func (r *LuetSystemRepository) Sync(force bool) (Repository, error) {
var repoUpdated bool = false
var treefs, metafs string
aurora := GetAurora()
Debug("Sync of the repository", r.Name, "in progress...")
c := r.Client()
if c == nil {
return nil, errors.New("No client could be generated from repository.")
}
file, err := c.DownloadFile("repository.yaml")
if err != nil {
return nil, errors.Wrap(err, "While downloading repository.yaml from "+r.GetUri())
}
dat, err := ioutil.ReadFile(file)
if err != nil {
return nil, errors.Wrap(err, "Error reading file "+file)
}
defer os.Remove(file)
// TODO: make it swappable
repo, err := NewLuetRepositoryFromYaml(dat, pkg.NewInMemoryDatabase(false))
// Retrieve remote repository.yaml for retrieve revision and date
file, err := c.DownloadFile(REPOSITORY_SPECFILE)
if err != nil {
return nil, errors.Wrap(err, "Error reading repository from file "+file)
return nil, errors.Wrap(err, "While downloading "+REPOSITORY_SPECFILE)
}
repobasedir := config.LuetCfg.GetSystem().GetRepoDatabaseDirPath(r.GetName())
repo, err := r.ReadSpecFile(file, false)
if err != nil {
return nil, err
}
// Remove temporary file that contains repository.html.
// Example: /tmp/HttpClient236052003
defer os.RemoveAll(file)
if r.Cached {
if !force {
localRepo, _ := r.ReadSpecFile(filepath.Join(repobasedir, REPOSITORY_SPECFILE), false)
if localRepo != nil {
if localRepo.GetRevision() == repo.GetRevision() &&
localRepo.GetLastUpdate() == repo.GetLastUpdate() {
repoUpdated = true
}
}
}
if r.GetTreePath() == "" {
treefs = filepath.Join(repobasedir, "treefs")
} else {
treefs = r.GetTreePath()
}
if r.GetMetaPath() == "" {
metafs = filepath.Join(repobasedir, "metafs")
} else {
metafs = r.GetMetaPath()
}
} else {
treefs, err = config.LuetCfg.GetSystem().TempDir("treefs")
if err != nil {
return nil, errors.Wrap(err, "Error met while creating tempdir for rootfs")
}
// Note: If we always remove them, later on, no other structure can access
// to the tree for e.g. to retrieve finalizers
metafs, err = config.LuetCfg.GetSystem().TempDir("metafs")
if err != nil {
return nil, errors.Wrap(err, "Error met whilte creating tempdir for metafs")
}
//defer os.RemoveAll(metafs)
}
archivetree, err := c.DownloadFile("tree.tar")
if err != nil {
return nil, errors.Wrap(err, "While downloading repository.yaml from "+r.GetUri())
}
defer os.RemoveAll(archivetree) // clean up
// POST: treeFile and metaFile are present. I check this inside
// ReadSpecFile and NewLuetSystemRepositoryFromYaml
treeFile, _ := repo.GetRepositoryFile(REPOFILE_TREE_KEY)
metaFile, _ := repo.GetRepositoryFile(REPOFILE_META_KEY)
treefs, err := ioutil.TempDir(os.TempDir(), "treefs")
if err != nil {
return nil, errors.Wrap(err, "Error met while creating tempdir for rootfs")
}
//defer os.RemoveAll(treefs) // clean up
if !repoUpdated {
// TODO: Following as option if archive as output?
// archive, err := ioutil.TempDir(os.TempDir(), "archive")
// if err != nil {
// return nil, errors.Wrap(err, "Error met while creating tempdir for rootfs")
// }
// defer os.RemoveAll(archive) // clean up
// Get Tree
a := compiler.NewPackageArtifact(treeFile.GetFileName())
artifactTree, err := c.DownloadArtifact(a)
if err != nil {
return nil, errors.Wrap(err, "While downloading "+treeFile.GetFileName())
}
defer os.Remove(artifactTree.GetPath())
err = helpers.Untar(archivetree, treefs, false)
if err != nil {
return nil, errors.Wrap(err, "Error met while unpacking rootfs")
artifactTree.SetChecksums(treeFile.GetChecksums())
artifactTree.SetCompressionType(treeFile.GetCompressionType())
err = artifactTree.Verify()
if err != nil {
return nil, errors.Wrap(err, "Tree integrity check failure")
}
Debug("Tree tarball for the repository " + r.GetName() + " downloaded correctly.")
// Get Repository Metadata
a = compiler.NewPackageArtifact(metaFile.GetFileName())
artifactMeta, err := c.DownloadArtifact(a)
if err != nil {
return nil, errors.Wrap(err, "While downloading "+metaFile.GetFileName())
}
defer os.Remove(artifactMeta.GetPath())
artifactMeta.SetChecksums(metaFile.GetChecksums())
artifactMeta.SetCompressionType(metaFile.GetCompressionType())
err = artifactMeta.Verify()
if err != nil {
return nil, errors.Wrap(err, "Metadata integrity check failure")
}
Debug("Metadata tarball for the repository " + r.GetName() + " downloaded correctly.")
if r.Cached {
// Copy updated repository.yaml file to repo dir now that the tree is synced.
err = helpers.CopyFile(file, filepath.Join(repobasedir, REPOSITORY_SPECFILE))
if err != nil {
return nil, errors.Wrap(err, "Error on update "+REPOSITORY_SPECFILE)
}
// Remove previous tree
os.RemoveAll(treefs)
// Remove previous meta dir
os.RemoveAll(metafs)
}
Debug("Decompress tree of the repository " + r.Name + "...")
err = artifactTree.Unpack(treefs, true)
if err != nil {
return nil, errors.Wrap(err, "Error met while unpacking tree")
}
// FIXME: It seems that tar with only one file doesn't create destination
// directory. I create directory directly for now.
os.MkdirAll(metafs, os.ModePerm)
err = artifactMeta.Unpack(metafs, true)
if err != nil {
return nil, errors.Wrap(err, "Error met while unpacking metadata")
}
tsec, _ := strconv.ParseInt(repo.GetLastUpdate(), 10, 64)
InfoC(
aurora.Bold(
aurora.Red(":house: Repository "+repo.GetName()+" revision: ")).String() +
aurora.Bold(aurora.Green(repo.GetRevision())).String() + " - " +
aurora.Bold(aurora.Green(time.Unix(tsec, 0).String())).String(),
)
} else {
Info("Repository", repo.GetName(), "is already up to date.")
}
meta, err := NewLuetSystemRepositoryMetadata(
filepath.Join(metafs, REPOSITORY_METAFILE), false,
)
if err != nil {
return nil, errors.Wrap(err, "While processing "+REPOSITORY_METAFILE)
}
repo.SetIndex(meta.ToArtifactIndex())
reciper := tree.NewInstallerRecipe(pkg.NewInMemoryDatabase(false))
err = reciper.Load(treefs)
if err != nil {
return nil, errors.Wrap(err, "Error met while unpacking rootfs")
}
repo.SetTree(reciper)
repo.SetTreePath(treefs)
repo.SetUri(r.GetUri())
// Copy the local available data to the one which was synced
// e.g. locally we can override the type (disk), or priority
// while remotely it could be advertized differently
repo.SetUrls(r.GetUrls())
repo.SetAuthentication(r.GetAuthentication())
repo.SetType(r.GetType())
repo.SetPriority(r.GetPriority())
repo.SetName(r.GetName())
InfoC(
aurora.Bold(
aurora.Yellow(":information_source: Repository "+repo.GetName()+" priority: ")).String() +
aurora.Bold(aurora.Green(repo.GetPriority())).String() + " - type " +
aurora.Bold(aurora.Green(repo.GetType())).String(),
)
return repo, nil
}
func (r *LuetSystemRepository) Serialize() (*LuetSystemRepositoryMetadata, LuetSystemRepositorySerialized) {
serialized := LuetSystemRepositorySerialized{
Name: r.Name,
Description: r.Description,
Urls: r.Urls,
Priority: r.Priority,
Type: r.Type,
Revision: r.Revision,
LastUpdate: r.LastUpdate,
RepositoryFiles: r.RepositoryFiles,
}
// Check if is needed set the index or simply use
// value returned by CleanPath
r.Index = r.Index.CleanPath()
meta := &LuetSystemRepositoryMetadata{
Index: []*compiler.PackageArtifact{},
}
for _, a := range r.Index {
art := a.(*compiler.PackageArtifact)
meta.Index = append(meta.Index, art)
}
return meta, serialized
}
func (r Repositories) Len() int { return len(r) }
func (r Repositories) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
func (r Repositories) Less(i, j int) bool {
return r[i].GetPriority() < r[j].GetPriority()
}
func (r Repositories) World() []pkg.Package {
func (r Repositories) World() pkg.Packages {
cache := map[string]pkg.Package{}
world := []pkg.Package{}
world := pkg.Packages{}
// Get Uniques. Walk in reverse so the definitions of most prio-repo overwrites lower ones
// In this way, when we will walk again later the deps sorting them by most higher prio we have better chance of success.
@@ -302,7 +774,7 @@ type PackageMatch struct {
Package pkg.Package
}
func (re Repositories) PackageMatches(p []pkg.Package) []PackageMatch {
func (re Repositories) PackageMatches(p pkg.Packages) []PackageMatch {
// TODO: Better heuristic. here we pick the first repo that contains the atom, sorted by priority but
// we should do a permutations and get the best match, and in case there are more solutions the user should be able to pick
sort.Sort(re)
@@ -323,14 +795,55 @@ PACKAGE:
}
func (re Repositories) Search(s string) []PackageMatch {
func (re Repositories) ResolveSelectors(p pkg.Packages) pkg.Packages {
// If a selector is given, get the best from each repo
sort.Sort(re) // respect prio
var matches pkg.Packages
PACKAGE:
for _, pack := range p {
REPOSITORY:
for _, r := range re {
if pack.IsSelector() {
c, err := r.GetTree().GetDatabase().FindPackageCandidate(pack)
// If FindPackageCandidate returns the same package, it means it couldn't find one.
// Skip this repository and keep looking.
if c.String() == pack.String() {
continue REPOSITORY
}
if err == nil {
matches = append(matches, c)
continue PACKAGE
}
} else {
// If it's not a selector, just append it
matches = append(matches, pack)
}
}
}
return matches
}
func (re Repositories) SearchPackages(p string, o LuetSearchOpts) []PackageMatch {
sort.Sort(re)
var term = regexp.MustCompile(s)
var matches []PackageMatch
var err error
for _, r := range re {
for _, pack := range r.GetTree().GetDatabase().World() {
if term.MatchString(pack.GetName()) {
var repoMatches pkg.Packages
switch o.Mode {
case SRegexPkg:
repoMatches, err = r.GetTree().GetDatabase().FindPackageMatch(p)
case SLabel:
repoMatches, err = r.GetTree().GetDatabase().FindPackageLabel(p)
case SRegexLabel:
repoMatches, err = r.GetTree().GetDatabase().FindPackageLabelMatch(p)
}
if err == nil && len(repoMatches) > 0 {
for _, pack := range repoMatches {
matches = append(matches, PackageMatch{Package: pack, Repo: r})
}
}
@@ -338,3 +851,15 @@ func (re Repositories) Search(s string) []PackageMatch {
return matches
}
func (re Repositories) SearchLabelMatch(s string) []PackageMatch {
return re.SearchPackages(s, LuetSearchOpts{Pattern: s, Mode: SRegexLabel})
}
func (re Repositories) SearchLabel(s string) []PackageMatch {
return re.SearchPackages(s, LuetSearchOpts{Pattern: s, Mode: SLabel})
}
func (re Repositories) Search(s string) []PackageMatch {
return re.SearchPackages(s, LuetSearchOpts{Pattern: s, Mode: SRegexPkg})
}

View File

@@ -18,11 +18,13 @@ package installer_test
import (
// . "github.com/mudler/luet/pkg/installer"
"io/ioutil"
"os"
"github.com/mudler/luet/pkg/compiler"
backend "github.com/mudler/luet/pkg/compiler/backend"
config "github.com/mudler/luet/pkg/config"
"github.com/mudler/luet/pkg/helpers"
. "github.com/mudler/luet/pkg/installer"
pkg "github.com/mudler/luet/pkg/package"
@@ -33,7 +35,7 @@ import (
var _ = Describe("Repository", func() {
Context("Generation", func() {
It("Generate repository metadat", func() {
It("Generate repository metadata", func() {
tmpdir, err := ioutil.TempDir("", "tree")
Expect(err).ToNot(HaveOccurred())
@@ -46,7 +48,7 @@ var _ = Describe("Repository", func() {
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(3))
compiler := compiler.NewLuetCompiler(backend.NewSimpleDockerBackend(), generalRecipe.GetDatabase())
compiler := compiler.NewLuetCompiler(backend.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), compiler.NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "b", Category: "test", Version: "1.0"})
Expect(err).ToNot(HaveOccurred())
@@ -61,7 +63,9 @@ var _ = Describe("Repository", func() {
Expect(spec.GetPreBuildSteps()).To(Equal([]string{"echo foo > /test", "echo bar > /test2", "chmod +x generate.sh"}))
spec.SetOutputPath(tmpdir)
artifact, err := compiler.Compile(2, false, spec)
compiler.SetConcurrency(1)
artifact, err := compiler.Compile(false, spec)
Expect(err).ToNot(HaveOccurred())
Expect(helpers.Exists(artifact.GetPath())).To(BeTrue())
Expect(helpers.Untar(artifact.GetPath(), tmpdir, false)).ToNot(HaveOccurred())
@@ -79,16 +83,117 @@ var _ = Describe("Repository", func() {
Expect(helpers.Exists(spec.Rel("b-test-1.0.package.tar"))).To(BeTrue())
Expect(helpers.Exists(spec.Rel("b-test-1.0.metadata.yaml"))).To(BeTrue())
repo, err := GenerateRepository("test", tmpdir, "local", 1, tmpdir, "../../tests/fixtures/buildable", pkg.NewInMemoryDatabase(false))
repo, err := GenerateRepository("test", "description", "disk", []string{tmpdir}, 1, tmpdir, []string{"../../tests/fixtures/buildable"}, pkg.NewInMemoryDatabase(false))
Expect(err).ToNot(HaveOccurred())
Expect(repo.GetName()).To(Equal("test"))
Expect(helpers.Exists(spec.Rel("repository.yaml"))).ToNot(BeTrue())
Expect(helpers.Exists(spec.Rel("tree.tar"))).ToNot(BeTrue())
err = repo.Write(tmpdir)
Expect(helpers.Exists(spec.Rel(REPOSITORY_SPECFILE))).ToNot(BeTrue())
Expect(helpers.Exists(spec.Rel(TREE_TARBALL + ".gz"))).ToNot(BeTrue())
Expect(helpers.Exists(spec.Rel(REPOSITORY_METAFILE + ".tar"))).ToNot(BeTrue())
err = repo.Write(tmpdir, false)
Expect(err).ToNot(HaveOccurred())
Expect(helpers.Exists(spec.Rel("repository.yaml"))).To(BeTrue())
Expect(helpers.Exists(spec.Rel("tree.tar"))).To(BeTrue())
Expect(helpers.Exists(spec.Rel(REPOSITORY_SPECFILE))).To(BeTrue())
Expect(helpers.Exists(spec.Rel(TREE_TARBALL + ".gz"))).To(BeTrue())
Expect(helpers.Exists(spec.Rel(REPOSITORY_METAFILE + ".tar"))).To(BeTrue())
})
It("Generate repository metadata of files ONLY referenced in a tree", func() {
tmpdir, err := ioutil.TempDir("", "tree")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(tmpdir) // clean up
generalRecipe := tree.NewCompilerRecipe(pkg.NewInMemoryDatabase(false))
err = generalRecipe.Load("../../tests/fixtures/buildable")
Expect(err).ToNot(HaveOccurred())
generalRecipe2 := tree.NewCompilerRecipe(pkg.NewInMemoryDatabase(false))
err = generalRecipe2.Load("../../tests/fixtures/finalizers")
Expect(err).ToNot(HaveOccurred())
Expect(len(generalRecipe2.GetDatabase().GetPackages())).To(Equal(1))
Expect(len(generalRecipe.GetDatabase().GetPackages())).To(Equal(3))
compiler2 := compiler.NewLuetCompiler(backend.NewSimpleDockerBackend(), generalRecipe2.GetDatabase(), compiler.NewDefaultCompilerOptions())
spec2, err := compiler2.FromPackage(&pkg.DefaultPackage{Name: "alpine", Category: "seed", Version: "1.0"})
Expect(err).ToNot(HaveOccurred())
compiler := compiler.NewLuetCompiler(backend.NewSimpleDockerBackend(), generalRecipe.GetDatabase(), compiler.NewDefaultCompilerOptions())
spec, err := compiler.FromPackage(&pkg.DefaultPackage{Name: "b", Category: "test", Version: "1.0"})
Expect(err).ToNot(HaveOccurred())
Expect(spec.GetPackage().GetPath()).ToNot(Equal(""))
Expect(spec2.GetPackage().GetPath()).ToNot(Equal(""))
tmpdir, err = ioutil.TempDir("", "tree")
Expect(err).ToNot(HaveOccurred())
defer os.RemoveAll(tmpdir) // clean up
Expect(spec.BuildSteps()).To(Equal([]string{"echo artifact5 > /test5", "echo artifact6 > /test6", "./generate.sh"}))
Expect(spec.GetPreBuildSteps()).To(Equal([]string{"echo foo > /test", "echo bar > /test2", "chmod +x generate.sh"}))
spec.SetOutputPath(tmpdir)
spec2.SetOutputPath(tmpdir)
compiler.SetConcurrency(1)
compiler2.SetConcurrency(1)
artifact, err := compiler.Compile(false, spec)
Expect(err).ToNot(HaveOccurred())
Expect(helpers.Exists(artifact.GetPath())).To(BeTrue())
Expect(helpers.Untar(artifact.GetPath(), tmpdir, false)).ToNot(HaveOccurred())
artifact2, err := compiler2.Compile(false, spec2)
Expect(err).ToNot(HaveOccurred())
Expect(helpers.Exists(artifact2.GetPath())).To(BeTrue())
Expect(helpers.Untar(artifact2.GetPath(), tmpdir, false)).ToNot(HaveOccurred())
Expect(helpers.Exists(spec.Rel("test5"))).To(BeTrue())
Expect(helpers.Exists(spec.Rel("test6"))).To(BeTrue())
content1, err := helpers.Read(spec.Rel("test5"))
Expect(err).ToNot(HaveOccurred())
content2, err := helpers.Read(spec.Rel("test6"))
Expect(err).ToNot(HaveOccurred())
Expect(content1).To(Equal("artifact5\n"))
Expect(content2).To(Equal("artifact6\n"))
Expect(helpers.Exists(spec.Rel("b-test-1.0.package.tar"))).To(BeTrue())
Expect(helpers.Exists(spec.Rel("b-test-1.0.metadata.yaml"))).To(BeTrue())
Expect(helpers.Exists(spec2.Rel("alpine-seed-1.0.package.tar"))).To(BeTrue())
Expect(helpers.Exists(spec2.Rel("alpine-seed-1.0.metadata.yaml"))).To(BeTrue())
repo, err := GenerateRepository("test", "description", "disk", []string{tmpdir}, 1, tmpdir, []string{"../../tests/fixtures/buildable"}, pkg.NewInMemoryDatabase(false))
Expect(err).ToNot(HaveOccurred())
Expect(repo.GetName()).To(Equal("test"))
Expect(helpers.Exists(spec.Rel(REPOSITORY_SPECFILE))).ToNot(BeTrue())
Expect(helpers.Exists(spec.Rel(TREE_TARBALL + ".gz"))).ToNot(BeTrue())
Expect(helpers.Exists(spec.Rel(REPOSITORY_METAFILE + ".tar"))).ToNot(BeTrue())
err = repo.Write(tmpdir, false)
Expect(err).ToNot(HaveOccurred())
Expect(helpers.Exists(spec.Rel(REPOSITORY_SPECFILE))).To(BeTrue())
Expect(helpers.Exists(spec.Rel(TREE_TARBALL + ".gz"))).To(BeTrue())
Expect(helpers.Exists(spec.Rel(REPOSITORY_METAFILE + ".tar"))).To(BeTrue())
// We check now that the artifact not referenced in the tree
// (spec2) is not indexed in the repository
repository, err := NewLuetSystemRepositoryFromYaml([]byte(`
name: "test"
type: "disk"
urls:
- "`+tmpdir+`"
`), pkg.NewInMemoryDatabase(false))
Expect(err).ToNot(HaveOccurred())
repos, err := repository.Sync(true)
Expect(err).ToNot(HaveOccurred())
_, err = repos.GetTree().GetDatabase().FindPackage(spec.GetPackage())
Expect(err).ToNot(HaveOccurred())
_, err = repos.GetTree().GetDatabase().FindPackage(spec2.GetPackage())
Expect(err).To(HaveOccurred()) // should throw error
})
})
Context("Matching packages", func() {
@@ -103,8 +208,8 @@ var _ = Describe("Repository", func() {
_, err = builder2.GetDatabase().CreatePackage(package2)
Expect(err).ToNot(HaveOccurred())
repo1 := &LuetRepository{Name: "test1", Tree: builder1}
repo2 := &LuetRepository{Name: "test2", Tree: builder2}
repo1 := &LuetSystemRepository{LuetRepository: &config.LuetRepository{Name: "test1"}, Tree: builder1}
repo2 := &LuetSystemRepository{LuetRepository: &config.LuetRepository{Name: "test2"}, Tree: builder2}
repositories := Repositories{repo1, repo2}
matches := repositories.PackageMatches([]pkg.Package{package1})
Expect(matches).To(Equal([]PackageMatch{{Repo: repo1, Package: package1}}))

View File

@@ -9,6 +9,6 @@ type System struct {
Target string
}
func (s *System) World() ([]pkg.Package, error) {
func (s *System) World() (pkg.Packages, error) {
return s.Database.World(), nil
}

View File

@@ -3,98 +3,231 @@ package logger
import (
"fmt"
"os"
"time"
"regexp"
. "github.com/mudler/luet/pkg/config"
"github.com/briandowns/spinner"
"github.com/kyokomi/emoji"
. "github.com/logrusorgru/aurora"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
var s *spinner.Spinner = spinner.New(spinner.CharSets[22], 100*time.Millisecond)
var s *spinner.Spinner = nil
var z *zap.Logger = nil
var aurora Aurora = nil
// TODO: handle this from configuration
var debug = false
func NewSpinner() {
if s == nil {
s = spinner.New(
spinner.CharSets[LuetCfg.GetGeneral().SpinnerCharset],
LuetCfg.GetGeneral().GetSpinnerMs())
}
}
func InitAurora() {
if aurora == nil {
aurora = NewAurora(LuetCfg.GetLogging().Color)
}
}
func GetAurora() Aurora {
return aurora
}
func ZapLogger() error {
var err error
if z == nil {
// TODO: test permission for open logfile.
cfg := zap.NewProductionConfig()
cfg.OutputPaths = []string{LuetCfg.GetLogging().Path}
cfg.Level = level2AtomicLevel(LuetCfg.GetLogging().Level)
cfg.ErrorOutputPaths = []string{}
if LuetCfg.GetLogging().JsonFormat {
cfg.Encoding = "json"
} else {
cfg.Encoding = "console"
}
cfg.DisableCaller = true
cfg.DisableStacktrace = true
cfg.EncoderConfig.TimeKey = "time"
cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
z, err = cfg.Build()
if err != nil {
fmt.Fprint(os.Stderr, "Error on initialize file logger: "+err.Error()+"\n")
return err
}
}
return nil
}
func Spinner(i int) {
var confLevel int
if LuetCfg.GetGeneral().Debug {
confLevel = 3
} else {
confLevel = level2Number(LuetCfg.GetLogging().Level)
}
if 2 > confLevel {
return
}
if i > 43 {
i = 43
}
if !debug && !s.Active() {
if s != nil && !s.Active() {
// s.UpdateCharSet(spinner.CharSets[i])
s.Start() // Start the spinner
}
}
func SpinnerText(suffix, prefix string) {
s.Lock()
defer s.Unlock()
if debug {
fmt.Println(fmt.Sprintf("%s %s",
Bold(Cyan(prefix)).String(),
Bold(Magenta(suffix)).BgBlack().String(),
))
} else {
s.Suffix = Bold(Magenta(suffix)).BgBlack().String()
s.Prefix = Bold(Cyan(prefix)).String()
if s != nil {
s.Lock()
defer s.Unlock()
if LuetCfg.GetGeneral().Debug {
fmt.Println(fmt.Sprintf("%s %s",
Bold(Cyan(prefix)).String(),
Bold(Magenta(suffix)).BgBlack().String(),
))
} else {
s.Suffix = Bold(Magenta(suffix)).BgBlack().String()
s.Prefix = Bold(Cyan(prefix)).String()
}
}
}
func SpinnerStop() {
if !debug {
var confLevel int
if LuetCfg.GetGeneral().Debug {
confLevel = 3
} else {
confLevel = level2Number(LuetCfg.GetLogging().Level)
}
if 2 > confLevel {
return
}
if s != nil {
s.Stop()
}
}
func msg(level string, msg ...interface{}) {
func level2Number(level string) int {
switch level {
case "error":
return 0
case "warning":
return 1
case "info":
return 2
default:
return 3
}
}
func log2File(level, msg string) {
switch level {
case "error":
z.Error(msg)
case "warning":
z.Warn(msg)
case "info":
z.Info(msg)
default:
z.Debug(msg)
}
}
func level2AtomicLevel(level string) zap.AtomicLevel {
switch level {
case "error":
return zap.NewAtomicLevelAt(zap.ErrorLevel)
case "warning":
return zap.NewAtomicLevelAt(zap.WarnLevel)
case "info":
return zap.NewAtomicLevelAt(zap.InfoLevel)
default:
return zap.NewAtomicLevelAt(zap.DebugLevel)
}
}
func msg(level string, withoutColor bool, msg ...interface{}) {
var message string
var confLevel, msgLevel int
if LuetCfg.GetGeneral().Debug {
confLevel = 3
} else {
confLevel = level2Number(LuetCfg.GetLogging().Level)
}
msgLevel = level2Number(level)
if msgLevel > confLevel {
return
}
for _, m := range msg {
message += " " + fmt.Sprintf("%v", m)
}
var levelMsg string
switch level {
case "warning":
levelMsg = Bold(Yellow(":construction: " + message)).BgBlack().String()
case "debug":
levelMsg = White(message).BgBlack().String()
case "info":
levelMsg = Bold(White(message)).BgBlack().String()
case "error":
levelMsg = Bold(Red(":bomb: " + message + ":fire:")).BgBlack().String()
if withoutColor || !LuetCfg.GetLogging().Color {
levelMsg = message
} else {
switch level {
case "warning":
levelMsg = Bold(Yellow(":construction: " + message)).BgBlack().String()
case "debug":
levelMsg = White(message).BgBlack().String()
case "info":
levelMsg = Bold(White(message)).BgBlack().String()
case "error":
levelMsg = Bold(Red(":bomb: " + message + ":fire:")).BgBlack().String()
}
}
levelMsg = emoji.Sprint(levelMsg)
if LuetCfg.GetLogging().EnableEmoji {
levelMsg = emoji.Sprint(levelMsg)
} else {
re := regexp.MustCompile(`[:][\w]+[:]`)
levelMsg = re.ReplaceAllString(levelMsg, "")
}
// if s.Active() {
// SpinnerText(levelMsg, "")
// return
// }
cmd := []interface{}{}
for _, f := range msg {
cmd = append(cmd, f)
if z != nil {
log2File(level, message)
}
fmt.Println(levelMsg)
//fmt.Println(cmd...)
}
func Warning(mess ...interface{}) {
msg("warning", mess...)
msg("warning", false, mess...)
if LuetCfg.GetGeneral().FatalWarns {
os.Exit(2)
}
}
func Debug(mess ...interface{}) {
msg("debug", mess...)
msg("debug", false, mess...)
}
func DebugC(mess ...interface{}) {
msg("debug", true, mess...)
}
func Info(mess ...interface{}) {
msg("info", mess...)
msg("info", false, mess...)
}
func InfoC(mess ...interface{}) {
msg("info", true, mess...)
}
func Error(mess ...interface{}) {
msg("error", mess...)
msg("error", false, mess...)
}
func Fatal(mess ...interface{}) {

View File

@@ -0,0 +1,23 @@
// Copyright © 2019-2020 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package pkg
type AnnotationKey string
const (
ConfigProtectAnnnotation AnnotationKey = "config_protect"
)

View File

@@ -33,7 +33,7 @@ type PackageSet interface {
GetPackage(ID string) (Package, error)
Clean() error
FindPackage(Package) (Package, error)
FindPackages(p Package) ([]Package, error)
FindPackages(p Package) (Packages, error)
UpdatePackage(p Package) error
GetAllPackages(packages chan Package) error
RemovePackage(Package) error
@@ -41,10 +41,13 @@ type PackageSet interface {
GetPackageFiles(Package) ([]string, error)
SetPackageFiles(*PackageFile) error
RemovePackageFiles(Package) error
FindPackageVersions(p Package) ([]Package, error)
World() []Package
FindPackageVersions(p Package) (Packages, error)
World() Packages
FindPackageCandidate(p Package) (Package, error)
FindPackageLabel(labelKey string) (Packages, error)
FindPackageLabelMatch(pattern string) (Packages, error)
FindPackageMatch(pattern string) (Packages, error)
}
type PackageFile struct {

View File

@@ -17,12 +17,13 @@ package pkg
import (
"encoding/base64"
"fmt"
"os"
"regexp"
"strconv"
"sync"
"time"
version "github.com/hashicorp/go-version"
"github.com/pkg/errors"
storm "github.com/asdine/storm"
@@ -228,24 +229,19 @@ func (db *BoltDatabase) getProvide(p Package) (Package, error) {
db.Unlock()
if !ok {
return nil, errors.New("No versions found for package")
return nil, errors.New(fmt.Sprintf("No versions found for: %s", p.HumanReadableString()))
}
for ve, _ := range versions {
v, err := version.NewVersion(p.GetVersion())
match, err := p.VersionMatchSelector(ve, nil)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "Error on match version")
}
constraints, err := version.NewConstraint(ve)
if err != nil {
return nil, err
}
if constraints.Check(v) {
if match {
pa, ok := db.ProvidesDatabase[p.GetPackageName()][ve]
if !ok {
return nil, errors.New("No versions found for package")
return nil, errors.New(fmt.Sprintf("No versions found for: %s", p.HumanReadableString()))
}
return pa, nil //pick the first (we shouldn't have providers that are conflicting)
// TODO: A find dbcall here would recurse, but would give chance to have providers of providers
@@ -314,12 +310,12 @@ func (db *BoltDatabase) RemovePackage(p Package) error {
var found DefaultPackage
err = bolt.Select(q.Eq("Name", p.GetName()), q.Eq("Category", p.GetCategory()), q.Eq("Version", p.GetVersion())).Limit(1).Delete(&found)
if err != nil {
return errors.Wrap(err, "No package found to delete")
return errors.New(fmt.Sprintf("Package not found: %s", p.HumanReadableString()))
}
return nil
}
func (db *BoltDatabase) World() []Package {
func (db *BoltDatabase) World() Packages {
var all []Package
// FIXME: This should all be locked in the db - for now forbid the solver to be run in threads.
@@ -329,7 +325,7 @@ func (db *BoltDatabase) World() []Package {
all = append(all, pack)
}
}
return all
return Packages(all)
}
func (db *BoltDatabase) FindPackageCandidate(p Package) (Package, error) {
@@ -343,7 +339,7 @@ func (db *BoltDatabase) FindPackageCandidate(p Package) (Package, error) {
if err != nil || len(packages) == 0 {
required = p
} else {
required = Best(packages)
required = packages.Best(nil)
}
return required, nil
@@ -356,7 +352,7 @@ func (db *BoltDatabase) FindPackageCandidate(p Package) (Package, error) {
// FindPackages return the list of the packages beloging to cat/name (any versions in requested range)
// FIXME: Optimize, see inmemorydb
func (db *BoltDatabase) FindPackages(p Package) ([]Package, error) {
func (db *BoltDatabase) FindPackages(p Package) (Packages, error) {
// Provides: Treat as the replaced package here
if provided, err := db.getProvide(p); err == nil {
p = provided
@@ -367,23 +363,19 @@ func (db *BoltDatabase) FindPackages(p Package) ([]Package, error) {
continue
}
v, err := version.NewVersion(w.GetVersion())
match, err := p.SelectorMatchVersion(w.GetVersion(), nil)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "Error on match selector")
}
constraints, err := version.NewConstraint(p.GetVersion())
if err != nil {
return nil, err
}
if constraints.Check(v) {
if match {
versionsInWorld = append(versionsInWorld, w)
}
}
return versionsInWorld, nil
return Packages(versionsInWorld), nil
}
// FindPackageVersions return the list of the packages beloging to cat/name
func (db *BoltDatabase) FindPackageVersions(p Package) ([]Package, error) {
func (db *BoltDatabase) FindPackageVersions(p Package) (Packages, error) {
var versionsInWorld []Package
for _, w := range db.World() {
if w.GetName() != p.GetName() || w.GetCategory() != p.GetCategory() {
@@ -392,5 +384,63 @@ func (db *BoltDatabase) FindPackageVersions(p Package) ([]Package, error) {
versionsInWorld = append(versionsInWorld, w)
}
return versionsInWorld, nil
return Packages(versionsInWorld), nil
}
func (db *BoltDatabase) FindPackageLabel(labelKey string) (Packages, error) {
var ans []Package
for _, k := range db.GetPackages() {
pack, err := db.GetPackage(k)
if err != nil {
return ans, err
}
if pack.HasLabel(labelKey) {
ans = append(ans, pack)
}
}
return Packages(ans), nil
}
func (db *BoltDatabase) FindPackageLabelMatch(pattern string) (Packages, error) {
var ans []Package
re := regexp.MustCompile(pattern)
if re == nil {
return nil, errors.New("Invalid regex " + pattern + "!")
}
for _, k := range db.GetPackages() {
pack, err := db.GetPackage(k)
if err != nil {
return ans, err
}
if pack.MatchLabel(re) {
ans = append(ans, pack)
}
}
return Packages(ans), nil
}
func (db *BoltDatabase) FindPackageMatch(pattern string) (Packages, error) {
var ans []Package
re := regexp.MustCompile(pattern)
if re == nil {
return nil, errors.New("Invalid regex " + pattern + "!")
}
for _, k := range db.GetPackages() {
pack, err := db.GetPackage(k)
if err != nil {
return ans, err
}
if re.MatchString(pack.HumanReadableString()) {
ans = append(ans, pack)
}
}
return Packages(ans), nil
}

View File

@@ -18,9 +18,10 @@ package pkg
import (
"encoding/base64"
"encoding/json"
"fmt"
"regexp"
"sync"
version "github.com/hashicorp/go-version"
"github.com/pkg/errors"
)
@@ -59,7 +60,7 @@ func (db *InMemoryDatabase) Get(s string) (string, error) {
defer db.Unlock()
pa, ok := db.Database[s]
if !ok {
return "", errors.New("No key found with that id")
return "", errors.New(fmt.Sprintf("No key found for: %s", s))
}
return pa, nil
}
@@ -178,16 +179,11 @@ func (db *InMemoryDatabase) getProvide(p Package) (Package, error) {
for ve, _ := range versions {
v, err := version.NewVersion(p.GetVersion())
match, err := p.VersionMatchSelector(ve, nil)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "Error on match version")
}
constraints, err := version.NewConstraint(ve)
if err != nil {
return nil, err
}
if constraints.Check(v) {
if match {
pa, ok := db.ProvidesDatabase[p.GetPackageName()][ve]
if !ok {
return nil, errors.New("No versions found for package")
@@ -228,7 +224,7 @@ func (db *InMemoryDatabase) FindPackage(p Package) (Package, error) {
}
// FindPackages return the list of the packages beloging to cat/name
func (db *InMemoryDatabase) FindPackageVersions(p Package) ([]Package, error) {
func (db *InMemoryDatabase) FindPackageVersions(p Package) (Packages, error) {
versions, ok := db.CacheNoVersion[p.GetPackageName()]
if !ok {
return nil, errors.New("No versions found for package")
@@ -241,11 +237,11 @@ func (db *InMemoryDatabase) FindPackageVersions(p Package) ([]Package, error) {
}
versionsInWorld = append(versionsInWorld, w)
}
return versionsInWorld, nil
return Packages(versionsInWorld), nil
}
// FindPackages return the list of the packages beloging to cat/name (any versions in requested range)
func (db *InMemoryDatabase) FindPackages(p Package) ([]Package, error) {
func (db *InMemoryDatabase) FindPackages(p Package) (Packages, error) {
// Provides: Treat as the replaced package here
if provided, err := db.getProvide(p); err == nil {
@@ -253,19 +249,16 @@ func (db *InMemoryDatabase) FindPackages(p Package) ([]Package, error) {
}
versions, ok := db.CacheNoVersion[p.GetPackageName()]
if !ok {
return nil, errors.New("No versions found for package")
return nil, errors.New(fmt.Sprintf("No versions found for: %s", p.HumanReadableString()))
}
var versionsInWorld []Package
for ve, _ := range versions {
v, err := version.NewVersion(ve)
match, err := p.SelectorMatchVersion(ve, nil)
if err != nil {
return nil, err
return nil, errors.Wrap(err, "Error on match selector")
}
constraints, err := version.NewConstraint(p.GetVersion())
if err != nil {
return nil, err
}
if constraints.Check(v) {
if match {
w, err := db.FindPackage(&DefaultPackage{Name: p.GetName(), Category: p.GetCategory(), Version: ve})
if err != nil {
return nil, errors.Wrap(err, "Cache mismatch - this shouldn't happen")
@@ -273,7 +266,7 @@ func (db *InMemoryDatabase) FindPackages(p Package) ([]Package, error) {
versionsInWorld = append(versionsInWorld, w)
}
}
return versionsInWorld, nil
return Packages(versionsInWorld), nil
}
func (db *InMemoryDatabase) UpdatePackage(p Package) error {
@@ -285,7 +278,7 @@ func (db *InMemoryDatabase) UpdatePackage(p Package) error {
return db.Set(p.GetFingerPrint(), enc)
return errors.New("Package not found")
return errors.New(fmt.Sprintf("Package not found: %s", p.HumanReadableString()))
}
func (db *InMemoryDatabase) GetPackages() []string {
@@ -310,7 +303,7 @@ func (db *InMemoryDatabase) GetPackageFiles(p Package) ([]string, error) {
pa, ok := db.FileDatabase[p.GetFingerPrint()]
if !ok {
return pa, errors.New("No key found with that id")
return pa, errors.New(fmt.Sprintf("No key found for: %s", p.HumanReadableString()))
}
return pa, nil
@@ -335,7 +328,7 @@ func (db *InMemoryDatabase) RemovePackage(p Package) error {
delete(db.Database, p.GetFingerPrint())
return nil
}
func (db *InMemoryDatabase) World() []Package {
func (db *InMemoryDatabase) World() Packages {
var all []Package
// FIXME: This should all be locked in the db - for now forbid the solver to be run in threads.
for _, k := range db.GetPackages() {
@@ -344,7 +337,7 @@ func (db *InMemoryDatabase) World() []Package {
all = append(all, pack)
}
}
return all
return Packages(all)
}
func (db *InMemoryDatabase) FindPackageCandidate(p Package) (Package, error) {
@@ -357,7 +350,7 @@ func (db *InMemoryDatabase) FindPackageCandidate(p Package) (Package, error) {
if err != nil || len(packages) == 0 {
required = p
} else {
required = Best(packages)
required = packages.Best(nil)
}
return required, nil
@@ -367,3 +360,62 @@ func (db *InMemoryDatabase) FindPackageCandidate(p Package) (Package, error) {
return required, err
}
func (db *InMemoryDatabase) FindPackageLabel(labelKey string) (Packages, error) {
var ans []Package
for _, k := range db.GetPackages() {
pack, err := db.GetPackage(k)
if err != nil {
return ans, err
}
if pack.HasLabel(labelKey) {
ans = append(ans, pack)
}
}
return Packages(ans), nil
}
func (db *InMemoryDatabase) FindPackageLabelMatch(pattern string) (Packages, error) {
var ans []Package
re := regexp.MustCompile(pattern)
if re == nil {
return nil, errors.New("Invalid regex " + pattern + "!")
}
for _, k := range db.GetPackages() {
pack, err := db.GetPackage(k)
if err != nil {
return ans, err
}
if pack.MatchLabel(re) {
ans = append(ans, pack)
}
}
return Packages(ans), nil
}
func (db *InMemoryDatabase) FindPackageMatch(pattern string) (Packages, error) {
var ans []Package
re := regexp.MustCompile(pattern)
if re == nil {
return nil, errors.New("Invalid regex " + pattern + "!")
}
for _, k := range db.GetPackages() {
pack, err := db.GetPackage(k)
if err != nil {
return ans, err
}
if re.MatchString(pack.HumanReadableString()) {
ans = append(ans, pack)
}
}
return Packages(ans), nil
}

View File

@@ -77,6 +77,27 @@ var _ = Describe("Database", func() {
})
It("Find specific package candidate", func() {
db := NewInMemoryDatabase(false)
a := NewPackage("A", "1.0", []*DefaultPackage{}, []*DefaultPackage{})
a1 := NewPackage("A", "1.1", []*DefaultPackage{}, []*DefaultPackage{})
a3 := NewPackage("A", "1.3", []*DefaultPackage{}, []*DefaultPackage{})
_, err := db.CreatePackage(a)
Expect(err).ToNot(HaveOccurred())
_, err = db.CreatePackage(a1)
Expect(err).ToNot(HaveOccurred())
_, err = db.CreatePackage(a3)
Expect(err).ToNot(HaveOccurred())
s := NewPackage("A", "=1.0", []*DefaultPackage{}, []*DefaultPackage{})
pack, err := db.FindPackageCandidate(s)
Expect(err).ToNot(HaveOccurred())
Expect(pack).To(Equal(a))
})
It("Provides replaces definitions", func() {
db := NewInMemoryDatabase(false)
a := NewPackage("A", "1.0", []*DefaultPackage{}, []*DefaultPackage{})

View File

@@ -17,19 +17,23 @@ package pkg
import (
"bytes"
"crypto/md5"
"encoding/json"
"fmt"
"io"
"path/filepath"
"sort"
"regexp"
"strconv"
"strings"
// . "github.com/mudler/luet/pkg/logger"
"github.com/mudler/luet/pkg/helpers"
version "github.com/mudler/luet/pkg/versioner"
gentoo "github.com/Sabayon/pkgs-checker/pkg/gentoo"
"github.com/crillab/gophersat/bf"
version "github.com/hashicorp/go-version"
"github.com/jinzhu/copier"
"github.com/ghodss/yaml"
"github.com/jinzhu/copier"
"github.com/pkg/errors"
)
// Package is a package interface (TBD)
@@ -38,29 +42,32 @@ type Package interface {
Encode(PackageDatabase) (string, error)
BuildFormula(PackageDatabase, PackageDatabase) ([]bf.Formula, error)
IsFlagged(bool) Package
Flagged() bool
GetFingerPrint() string
GetPackageName() string
Requires([]*DefaultPackage) Package
Conflicts([]*DefaultPackage) Package
Revdeps(PackageDatabase) []Package
Revdeps(PackageDatabase) Packages
ExpandedRevdeps(definitiondb PackageDatabase, visited map[string]interface{}) Packages
LabelDeps(PackageDatabase, string) Packages
GetProvides() []*DefaultPackage
SetProvides([]*DefaultPackage) Package
GetRequires() []*DefaultPackage
GetConflicts() []*DefaultPackage
Expand(PackageDatabase) ([]Package, error)
Expand(PackageDatabase) (Packages, error)
SetCategory(string)
GetName() string
SetName(string)
GetCategory() string
GetVersion() string
SetVersion(string)
RequiresContains(PackageDatabase, Package) (bool, error)
Matches(m Package) bool
Bigger(m Package) bool
BumpBuildVersion() error
AddUse(use string)
RemoveUse(use string)
@@ -82,17 +89,41 @@ type Package interface {
SetLicense(string)
GetLicense() string
AddLabel(string, string)
GetLabels() map[string]string
HasLabel(string) bool
MatchLabel(*regexp.Regexp) bool
AddAnnotation(string, string)
GetAnnotations() map[string]string
HasAnnotation(string) bool
MatchAnnotation(*regexp.Regexp) bool
IsHidden() bool
IsSelector() bool
VersionMatchSelector(string, version.Versioner) (bool, error)
SelectorMatchVersion(string, version.Versioner) (bool, error)
String() string
HumanReadableString() string
HashFingerprint(string) string
SetBuildTimestamp(s string)
GetBuildTimestamp() string
Clone() Package
}
type Tree interface {
GetPackageSet() PackageDatabase
Prelude() string // A tree might have a prelude to be able to consume a tree
SetPackageSet(s PackageDatabase)
World() ([]Package, error)
World() (Packages, error)
FindPackage(Package) (Package, error)
}
type Packages []Package
// >> Unmarshallers
// DefaultPackageFromYaml decodes a package from yaml bytes
func DefaultPackageFromYaml(yml []byte) (DefaultPackage, error) {
@@ -126,25 +157,29 @@ func (t *DefaultPackage) JSON() ([]byte, error) {
// DefaultPackage represent a standard package definition
type DefaultPackage struct {
ID int `json:"-" storm:"id,increment"` // primary key with auto increment
Name string `json:"name"` // Affects YAML field names too.
Version string `json:"version"` // Affects YAML field names too.
Category string `json:"category"` // Affects YAML field names too.
UseFlags []string `json:"use_flags"` // Affects YAML field names too.
State State
PackageRequires []*DefaultPackage `json:"requires"` // Affects YAML field names too.
PackageConflicts []*DefaultPackage `json:"conflicts"` // Affects YAML field names too.
IsSet bool `json:"set"` // Affects YAML field names too.
Provides []*DefaultPackage `json:"provides"` // Affects YAML field names too.
ID int `storm:"id,increment" json:"id"` // primary key with auto increment
Name string `json:"name"` // Affects YAML field names too.
Version string `json:"version"` // Affects YAML field names too.
Category string `json:"category"` // Affects YAML field names too.
UseFlags []string `json:"use_flags,omitempty"` // Affects YAML field names too.
State State `json:"state,omitempty"`
PackageRequires []*DefaultPackage `json:"requires"` // Affects YAML field names too.
PackageConflicts []*DefaultPackage `json:"conflicts"` // Affects YAML field names too.
Provides []*DefaultPackage `json:"provides,omitempty"` // Affects YAML field names too.
Hidden bool `json:"hidden,omitempty"` // Affects YAML field names too.
// TODO: Annotations?
// Annotations are used for core features/options
Annotations map[string]string `json:"annotations,omitempty"` // Affects YAML field names too
// Path is set only internally when tree is loaded from disk
Path string `json:"path,omitempty"`
Description string `json:"description"`
Uri []string `json:"uri"`
License string `json:"license"`
Description string `json:"description,omitempty"`
Uri []string `json:"uri,omitempty"`
License string `json:"license,omitempty"`
BuildTimestamp string `json:"buildtimestamp,omitempty"`
Labels map[string]string `json:"labels,omitempty"` // Affects YAML field names too.
}
// State represent the package state
@@ -152,13 +187,19 @@ type State string
// NewPackage returns a new package
func NewPackage(name, version string, requires []*DefaultPackage, conflicts []*DefaultPackage) *DefaultPackage {
return &DefaultPackage{Name: name, Version: version, PackageRequires: requires, PackageConflicts: conflicts}
return &DefaultPackage{
Name: name,
Version: version,
PackageRequires: requires,
PackageConflicts: conflicts,
Labels: nil,
}
}
func (p *DefaultPackage) String() string {
b, err := p.JSON()
if err != nil {
return fmt.Sprintf("{ id: \"%d\", name: \"%s\" }", p.ID, p.Name)
return fmt.Sprintf("{ id: \"%d\", name: \"%s\", version: \"%s\", category: \"%s\" }", p.ID, p.Name, p.Version, p.Category)
}
return fmt.Sprintf("%s", string(b))
}
@@ -169,10 +210,40 @@ func (p *DefaultPackage) GetFingerPrint() string {
return fmt.Sprintf("%s-%s-%s", p.Name, p.Category, p.Version)
}
func (p *DefaultPackage) HashFingerprint(salt string) string {
h := md5.New()
io.WriteString(h, fmt.Sprintf("%s-%s", p.GetFingerPrint(), salt))
return fmt.Sprintf("%x", h.Sum(nil))
}
func (p *DefaultPackage) HumanReadableString() string {
return fmt.Sprintf("%s/%s-%s", p.Category, p.Name, p.Version)
}
func FromString(s string) Package {
var unescaped DefaultPackage
err := json.Unmarshal([]byte(s), &unescaped)
if err != nil {
return &unescaped
}
return &unescaped
}
func (p *DefaultPackage) GetPackageName() string {
return fmt.Sprintf("%s-%s", p.Name, p.Category)
}
// GetBuildTimestamp returns the package build timestamp
func (p *DefaultPackage) GetBuildTimestamp() string {
return p.BuildTimestamp
}
// SetBuildTimestamp sets the package Build timestamp
func (p *DefaultPackage) SetBuildTimestamp(s string) {
p.BuildTimestamp = s
}
// GetPath returns the path where the definition file was found
func (p *DefaultPackage) GetPath() string {
return p.Path
@@ -190,6 +261,26 @@ func (p *DefaultPackage) IsSelector() bool {
return strings.ContainsAny(p.GetVersion(), "<>=")
}
func (p *DefaultPackage) IsHidden() bool {
return p.Hidden
}
func (p *DefaultPackage) HasLabel(label string) bool {
return helpers.MapHasKey(&p.Labels, label)
}
func (p *DefaultPackage) MatchLabel(r *regexp.Regexp) bool {
return helpers.MapMatchRegex(&p.Labels, r)
}
func (p *DefaultPackage) HasAnnotation(label string) bool {
return helpers.MapHasKey(&p.Annotations, label)
}
func (p *DefaultPackage) MatchAnnotation(r *regexp.Regexp) bool {
return helpers.MapMatchRegex(&p.Annotations, r)
}
// AddUse adds a use to a package
func (p *DefaultPackage) AddUse(use string) {
for _, v := range p.UseFlags {
@@ -220,7 +311,6 @@ func (p *DefaultPackage) Encode(db PackageDatabase) (string, error) {
func (p *DefaultPackage) Yaml() ([]byte, error) {
j, err := p.JSON()
if err != nil {
return []byte{}, err
}
y, err := yaml.JSONToYAML(j)
@@ -231,15 +321,6 @@ func (p *DefaultPackage) Yaml() ([]byte, error) {
return y, nil
}
func (p *DefaultPackage) IsFlagged(b bool) Package {
p.IsSet = b
return p
}
func (p *DefaultPackage) Flagged() bool {
return p.IsSet
}
func (p *DefaultPackage) GetName() string {
return p.Name
}
@@ -247,6 +328,9 @@ func (p *DefaultPackage) GetName() string {
func (p *DefaultPackage) GetVersion() string {
return p.Version
}
func (p *DefaultPackage) SetVersion(v string) {
p.Version = v
}
func (p *DefaultPackage) GetDescription() string {
return p.Description
}
@@ -271,9 +355,32 @@ func (p *DefaultPackage) GetCategory() string {
func (p *DefaultPackage) SetCategory(s string) {
p.Category = s
}
func (p *DefaultPackage) SetName(s string) {
p.Name = s
}
func (p *DefaultPackage) GetUses() []string {
return p.UseFlags
}
func (p *DefaultPackage) AddLabel(k, v string) {
if p.Labels == nil {
p.Labels = make(map[string]string, 0)
}
p.Labels[k] = v
}
func (p *DefaultPackage) AddAnnotation(k, v string) {
if p.Annotations == nil {
p.Annotations = make(map[string]string, 0)
}
p.Annotations[k] = v
}
func (p *DefaultPackage) GetLabels() map[string]string {
return p.Labels
}
func (p *DefaultPackage) GetAnnotations() map[string]string {
return p.Annotations
}
func (p *DefaultPackage) GetProvides() []*DefaultPackage {
return p.Provides
}
@@ -306,31 +413,20 @@ func (p *DefaultPackage) Matches(m Package) bool {
}
return false
}
func (p *DefaultPackage) Bigger(m Package) bool {
low := Lower([]Package{p, m})
if low.Matches(m) {
return true
}
return false
}
func (p *DefaultPackage) Expand(definitiondb PackageDatabase) ([]Package, error) {
var versionsInWorld []Package
func (p *DefaultPackage) Expand(definitiondb PackageDatabase) (Packages, error) {
var versionsInWorld Packages
all, err := definitiondb.FindPackages(p)
if err != nil {
return nil, err
}
for _, w := range all {
v, err := version.NewVersion(w.GetVersion())
match, err := p.SelectorMatchVersion(w.GetVersion(), nil)
if err != nil {
return nil, err
}
constraints, err := version.NewConstraint(p.GetVersion())
if err != nil {
return nil, err
}
if constraints.Check(v) {
if match {
versionsInWorld = append(versionsInWorld, w)
}
}
@@ -338,8 +434,8 @@ func (p *DefaultPackage) Expand(definitiondb PackageDatabase) ([]Package, error)
return versionsInWorld, nil
}
func (p *DefaultPackage) Revdeps(definitiondb PackageDatabase) []Package {
var versionsInWorld []Package
func (p *DefaultPackage) Revdeps(definitiondb PackageDatabase) Packages {
var versionsInWorld Packages
for _, w := range definitiondb.World() {
if w.Matches(p) {
continue
@@ -355,11 +451,74 @@ func (p *DefaultPackage) Revdeps(definitiondb PackageDatabase) []Package {
return versionsInWorld
}
// ExpandedRevdeps returns the package reverse dependencies,
// matching also selectors in versions (>, <, >=, <=)
func (p *DefaultPackage) ExpandedRevdeps(definitiondb PackageDatabase, visited map[string]interface{}) Packages {
var versionsInWorld Packages
if _, ok := visited[p.HumanReadableString()]; ok {
return versionsInWorld
}
visited[p.HumanReadableString()] = true
for _, w := range definitiondb.World() {
if w.Matches(p) {
continue
}
match := false
for _, re := range w.GetRequires() {
if re.Matches(p) {
match = true
}
if !match {
packages, _ := re.Expand(definitiondb)
for _, pa := range packages {
if pa.Matches(p) {
match = true
}
}
}
// if ok, _ := w.RequiresContains(definitiondb, p); ok {
}
if match {
versionsInWorld = append(versionsInWorld, w)
versionsInWorld = append(versionsInWorld, w.ExpandedRevdeps(definitiondb, visited).Unique()...)
}
// }
}
//visited[p.HumanReadableString()] = true
return versionsInWorld.Unique()
}
func (p *DefaultPackage) LabelDeps(definitiondb PackageDatabase, labelKey string) Packages {
var pkgsWithLabelInWorld Packages
// TODO: check if integrate some index to improve
// research instead of iterate all list.
for _, w := range definitiondb.World() {
if w.HasLabel(labelKey) {
pkgsWithLabelInWorld = append(pkgsWithLabelInWorld, w)
}
}
return pkgsWithLabelInWorld
}
func DecodePackage(ID string, db PackageDatabase) (Package, error) {
return db.GetPackage(ID)
}
func (pack *DefaultPackage) RequiresContains(definitiondb PackageDatabase, s Package) (bool, error) {
func (pack *DefaultPackage) scanRequires(definitiondb PackageDatabase, s Package, visited map[string]interface{}) (bool, error) {
if _, ok := visited[pack.HumanReadableString()]; ok {
return false, nil
}
visited[pack.HumanReadableString()] = true
p, err := definitiondb.FindPackage(pack)
if err != nil {
p = pack //relax things
@@ -377,14 +536,27 @@ func (pack *DefaultPackage) RequiresContains(definitiondb PackageDatabase, s Pac
return true, nil
}
}
if contains, err := re.RequiresContains(definitiondb, s); err == nil && contains {
if contains, err := re.scanRequires(definitiondb, s, visited); err == nil && contains {
return true, nil
}
}
return false, nil
}
func Lower(set []Package) Package {
// RequiresContains recursively scans into the database packages dependencies to find a match with the given package
// It is used by the solver during uninstall.
func (pack *DefaultPackage) RequiresContains(definitiondb PackageDatabase, s Package) (bool, error) {
return pack.scanRequires(definitiondb, s, make(map[string]interface{}))
}
// Best returns the best version of the package (the most bigger) from a list
// Accepts a versioner interface to change the ordering policy. If null is supplied
// It defaults to version.WrappedVersioner which supports both semver and debian versioning
func (set Packages) Best(v version.Versioner) Package {
if v == nil {
v = &version.WrappedVersioner{}
}
var versionsMap map[string]Package = make(map[string]Package)
if len(set) == 0 {
panic("Best needs a list with elements")
@@ -395,43 +567,28 @@ func Lower(set []Package) Package {
versionsRaw = append(versionsRaw, p.GetVersion())
versionsMap[p.GetVersion()] = p
}
sorted := v.Sort(versionsRaw)
versions := make([]*version.Version, len(versionsRaw))
for i, raw := range versionsRaw {
v, _ := version.NewVersion(raw)
versions[i] = v
}
// After this, the versions are properly sorted
sort.Sort(version.Collection(versions))
return versionsMap[versions[0].Original()]
return versionsMap[sorted[len(sorted)-1]]
}
func Best(set []Package) Package {
var versionsMap map[string]Package = make(map[string]Package)
if len(set) == 0 {
panic("Best needs a list with elements")
}
versionsRaw := []string{}
func (set Packages) Unique() Packages {
var result Packages
uniq := make(map[string]Package)
for _, p := range set {
versionsRaw = append(versionsRaw, p.GetVersion())
versionsMap[p.GetVersion()] = p
uniq[p.GetFingerPrint()] = p
}
versions := make([]*version.Version, len(versionsRaw))
for i, raw := range versionsRaw {
v, _ := version.NewVersion(raw)
versions[i] = v
for _, p := range uniq {
result = append(result, p)
}
// After this, the versions are properly sorted
sort.Sort(version.Collection(versions))
return versionsMap[versions[len(versions)-1].Original()]
return result
}
func (pack *DefaultPackage) BuildFormula(definitiondb PackageDatabase, db PackageDatabase) ([]bf.Formula, error) {
func (pack *DefaultPackage) buildFormula(definitiondb PackageDatabase, db PackageDatabase, visited map[string]interface{}) ([]bf.Formula, error) {
if _, ok := visited[pack.HumanReadableString()]; ok {
return nil, nil
}
visited[pack.HumanReadableString()] = true
p, err := definitiondb.FindPackage(pack)
if err != nil {
p = pack // Relax failures and trust the def
@@ -444,6 +601,22 @@ func (pack *DefaultPackage) BuildFormula(definitiondb PackageDatabase, db Packag
A := bf.Var(encodedA)
var formulas []bf.Formula
// Do conflict with other packages versions (if A is selected, then conflict with other versions of A)
packages, _ := definitiondb.FindPackageVersions(p)
if len(packages) > 0 {
for _, cp := range packages {
encodedB, err := cp.Encode(db)
if err != nil {
return nil, err
}
B := bf.Var(encodedB)
if !p.Matches(cp) {
formulas = append(formulas, bf.Or(bf.Not(A), bf.Or(bf.Not(A), bf.Not(B))))
}
}
}
for _, requiredDef := range p.GetRequires() {
required, err := definitiondb.FindPackage(requiredDef)
if err != nil || requiredDef.IsSelector() {
@@ -455,62 +628,60 @@ func (pack *DefaultPackage) BuildFormula(definitiondb PackageDatabase, db Packag
if err != nil || len(packages) == 0 {
required = requiredDef
} else {
if len(packages) == 1 {
required = packages[0]
} else {
var ALO, priorityConstraints, priorityALO []bf.Formula
// Try to prio best match
// Force the solver to consider first our candidate (if does exists).
// Then builds ALO and AMO for the requires.
c, candidateErr := definitiondb.FindPackageCandidate(requiredDef)
var C bf.Formula
if candidateErr == nil {
// We have a desired candidate, try to look a solution with that included first
for _, o := range packages {
encodedB, err := o.Encode(db)
if err != nil {
return nil, err
}
B := bf.Var(encodedB)
if !o.Matches(c) {
priorityConstraints = append(priorityConstraints, bf.Not(B))
priorityALO = append(priorityALO, B)
}
}
encodedC, err := c.Encode(db)
if err != nil {
return nil, err
}
C = bf.Var(encodedC)
// Or the Candidate is true, or all the others might be not true
// This forces the CDCL sat implementation to look first at a solution with C=true
formulas = append(formulas, bf.Or(bf.Or(C, bf.Or(priorityConstraints...)), bf.Or(bf.Not(C), bf.Or(priorityALO...))))
}
var ALO, priorityConstraints, priorityALO []bf.Formula
// AMO - At most one
// Try to prio best match
// Force the solver to consider first our candidate (if does exists).
// Then builds ALO and AMO for the requires.
c, candidateErr := definitiondb.FindPackageCandidate(requiredDef)
var C bf.Formula
if candidateErr == nil {
// We have a desired candidate, try to look a solution with that included first
for _, o := range packages {
encodedB, err := o.Encode(db)
if err != nil {
return nil, err
}
B := bf.Var(encodedB)
ALO = append(ALO, B)
for _, i := range packages {
encodedI, err := i.Encode(db)
if err != nil {
return nil, err
}
I := bf.Var(encodedI)
if !o.Matches(i) {
formulas = append(formulas, bf.Or(bf.Not(I), bf.Not(B)))
}
if !o.Matches(c) {
priorityConstraints = append(priorityConstraints, bf.Not(B))
priorityALO = append(priorityALO, B)
}
}
formulas = append(formulas, bf.Or(ALO...)) // ALO - At least one
continue
encodedC, err := c.Encode(db)
if err != nil {
return nil, err
}
C = bf.Var(encodedC)
// Or the Candidate is true, or all the others might be not true
// This forces the CDCL sat implementation to look first at a solution with C=true
formulas = append(formulas, bf.Or(bf.Not(A), bf.Or(bf.Or(C, bf.Or(priorityConstraints...)), bf.Or(bf.Not(C), bf.Or(priorityALO...)))))
}
// AMO - At most one
for _, o := range packages {
encodedB, err := o.Encode(db)
if err != nil {
return nil, err
}
B := bf.Var(encodedB)
ALO = append(ALO, B)
for _, i := range packages {
encodedI, err := i.Encode(db)
if err != nil {
return nil, err
}
I := bf.Var(encodedI)
if !o.Matches(i) {
formulas = append(formulas, bf.Or(bf.Not(A), bf.Or(bf.Not(I), bf.Not(B))))
}
}
}
formulas = append(formulas, bf.Or(bf.Not(A), bf.Or(ALO...))) // ALO - At least one
continue
}
}
encodedB, err := required.Encode(db)
@@ -519,8 +690,8 @@ func (pack *DefaultPackage) BuildFormula(definitiondb PackageDatabase, db Packag
}
B := bf.Var(encodedB)
formulas = append(formulas, bf.Or(bf.Not(A), B))
f, err := required.BuildFormula(definitiondb, db)
r := required.(*DefaultPackage) // We know since the implementation is DefaultPackage, that can be only DefaultPackage
f, err := r.buildFormula(definitiondb, db, visited)
if err != nil {
return nil, err
}
@@ -538,20 +709,25 @@ func (pack *DefaultPackage) BuildFormula(definitiondb PackageDatabase, db Packag
if err != nil || len(packages) == 0 {
required = requiredDef
} else {
for _, p := range packages {
encodedB, err := p.Encode(db)
if err != nil {
return nil, err
if len(packages) == 1 {
required = packages[0]
} else {
for _, p := range packages {
encodedB, err := p.Encode(db)
if err != nil {
return nil, err
}
B := bf.Var(encodedB)
formulas = append(formulas, bf.Or(bf.Not(A),
bf.Not(B)))
r := p.(*DefaultPackage) // We know since the implementation is DefaultPackage, that can be only DefaultPackage
f, err := r.buildFormula(definitiondb, db, visited)
if err != nil {
return nil, err
}
formulas = append(formulas, f...)
}
B := bf.Var(encodedB)
formulas = append(formulas, bf.Or(bf.Not(A),
bf.Not(B)))
f, err := p.BuildFormula(definitiondb, db)
if err != nil {
return nil, err
}
formulas = append(formulas, f...)
continue
}
}
}
@@ -564,23 +740,28 @@ func (pack *DefaultPackage) BuildFormula(definitiondb PackageDatabase, db Packag
formulas = append(formulas, bf.Or(bf.Not(A),
bf.Not(B)))
f, err := required.BuildFormula(definitiondb, db)
r := required.(*DefaultPackage) // We know since the implementation is DefaultPackage, that can be only DefaultPackage
f, err := r.buildFormula(definitiondb, db, visited)
if err != nil {
return nil, err
}
formulas = append(formulas, f...)
}
return formulas, nil
}
func (pack *DefaultPackage) BuildFormula(definitiondb PackageDatabase, db PackageDatabase) ([]bf.Formula, error) {
return pack.buildFormula(definitiondb, db, make(map[string]interface{}))
}
func (p *DefaultPackage) Explain() {
fmt.Println("====================")
fmt.Println("Name: ", p.GetName())
fmt.Println("Category: ", p.GetCategory())
fmt.Println("Version: ", p.GetVersion())
fmt.Println("Installed: ", p.IsSet)
for _, req := range p.GetRequires() {
fmt.Println("\t-> ", req)
@@ -593,3 +774,108 @@ func (p *DefaultPackage) Explain() {
fmt.Println("====================")
}
func (p *DefaultPackage) BumpBuildVersion() error {
cat := p.Category
if cat == "" {
// Use fake category for parse package
cat = "app"
}
gp, err := gentoo.ParsePackageStr(
fmt.Sprintf("%s/%s-%s", cat,
p.Name, p.GetVersion()))
if err != nil {
return errors.Wrap(err, "Error on parser version")
}
buildPrefix := ""
buildId := 0
if gp.VersionBuild != "" {
// Check if version build is a number
buildId, err = strconv.Atoi(gp.VersionBuild)
if err == nil {
goto end
}
// POST: is not only a number
// TODO: check if there is a better way to handle all use cases.
r1 := regexp.MustCompile(`^r[0-9]*$`)
if r1 == nil {
return errors.New("Error on create regex for -r[0-9]")
}
if r1.MatchString(gp.VersionBuild) {
buildId, err = strconv.Atoi(strings.ReplaceAll(gp.VersionBuild, "r", ""))
if err == nil {
buildPrefix = "r"
goto end
}
}
p1 := regexp.MustCompile(`^p[0-9]*$`)
if p1 == nil {
return errors.New("Error on create regex for -p[0-9]")
}
if p1.MatchString(gp.VersionBuild) {
buildId, err = strconv.Atoi(strings.ReplaceAll(gp.VersionBuild, "p", ""))
if err == nil {
buildPrefix = "p"
goto end
}
}
rc1 := regexp.MustCompile(`^rc[0-9]*$`)
if rc1 == nil {
return errors.New("Error on create regex for -rc[0-9]")
}
if rc1.MatchString(gp.VersionBuild) {
buildId, err = strconv.Atoi(strings.ReplaceAll(gp.VersionBuild, "rc", ""))
if err == nil {
buildPrefix = "rc"
goto end
}
}
// Check if version build contains a dot
dotIdx := strings.LastIndex(gp.VersionBuild, ".")
if dotIdx > 0 {
buildPrefix = gp.VersionBuild[0 : dotIdx+1]
bVersion := gp.VersionBuild[dotIdx+1:]
buildId, err = strconv.Atoi(bVersion)
if err == nil {
goto end
}
}
buildPrefix = gp.VersionBuild + "."
buildId = 0
}
end:
buildId++
p.Version = fmt.Sprintf("%s%s+%s%d",
gp.Version, gp.VersionSuffix, buildPrefix, buildId)
return nil
}
func (p *DefaultPackage) SelectorMatchVersion(ver string, v version.Versioner) (bool, error) {
if !p.IsSelector() {
return false, errors.New("Package is not a selector")
}
if v == nil {
v = &version.WrappedVersioner{}
}
return v.ValidateSelector(ver, p.GetVersion()), nil
}
func (p *DefaultPackage) VersionMatchSelector(selector string, v version.Versioner) (bool, error) {
if v == nil {
v = &version.WrappedVersioner{}
}
return v.ValidateSelector(p.GetVersion(), selector), nil
}

View File

@@ -18,11 +18,15 @@ package pkg_test
import (
"testing"
. "github.com/mudler/luet/cmd"
config "github.com/mudler/luet/pkg/config"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestSolver(t *testing.T) {
RegisterFailHandler(Fail)
LoadConfig(config.LuetCfg)
RunSpecs(t, "Package Suite")
}

View File

@@ -16,6 +16,8 @@
package pkg_test
import (
"regexp"
. "github.com/mudler/luet/pkg/package"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@@ -23,11 +25,28 @@ import (
var _ = Describe("Package", func() {
Context("Encoding/Decoding", func() {
a := &DefaultPackage{Name: "test", Version: "1", Category: "t"}
a1 := NewPackage("A", "1.0", []*DefaultPackage{}, []*DefaultPackage{})
It("Encodes and decodes correctly", func() {
Expect(a.String()).ToNot(Equal(""))
p := FromString(a.String())
Expect(p).To(Equal(a))
})
It("Generates packages fingerprint's hashes", func() {
Expect(a.HashFingerprint("")).ToNot(Equal(a1.HashFingerprint("")))
Expect(a.HashFingerprint("")).To(Equal("76972ef6991ec6102f33b401105c1351"))
})
})
Context("Simple package", func() {
a := NewPackage("A", ">=1.0", []*DefaultPackage{}, []*DefaultPackage{})
a1 := NewPackage("A", "1.0", []*DefaultPackage{}, []*DefaultPackage{})
a11 := NewPackage("A", "1.1", []*DefaultPackage{}, []*DefaultPackage{})
a01 := NewPackage("A", "0.1", []*DefaultPackage{}, []*DefaultPackage{})
re := regexp.MustCompile("project[0-9][=].*")
It("Expands correctly", func() {
definitions := NewInMemoryDatabase(false)
for _, p := range []Package{a1, a11, a01} {
@@ -40,8 +59,66 @@ var _ = Describe("Package", func() {
Expect(lst).To(ContainElement(a1))
Expect(lst).ToNot(ContainElement(a01))
Expect(len(lst)).To(Equal(2))
p := Best(lst)
p := lst.Best(nil)
Expect(p).To(Equal(a11))
// Test annotation with null map
Expect(a.MatchAnnotation(re)).To(Equal(false))
})
})
Context("Find label on packages", func() {
a := NewPackage("A", ">=1.0", []*DefaultPackage{}, []*DefaultPackage{})
a.AddLabel("project1", "test1")
a.AddLabel("label2", "value1")
b := NewPackage("B", "1.0", []*DefaultPackage{}, []*DefaultPackage{})
b.AddLabel("project2", "test2")
b.AddLabel("label2", "value1")
It("Expands correctly", func() {
var err error
definitions := NewInMemoryDatabase(false)
for _, p := range []Package{a, b} {
_, err = definitions.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
re := regexp.MustCompile("project[0-9][=].*")
Expect(err).ToNot(HaveOccurred())
Expect(re).ToNot(BeNil())
Expect(a.HasLabel("label2")).To(Equal(true))
Expect(a.HasLabel("label3")).To(Equal(false))
Expect(a.HasLabel("project1")).To(Equal(true))
Expect(b.HasLabel("project2")).To(Equal(true))
Expect(b.HasLabel("label2")).To(Equal(true))
Expect(b.MatchLabel(re)).To(Equal(true))
Expect(a.MatchLabel(re)).To(Equal(true))
})
})
Context("Find annotations on packages", func() {
a := NewPackage("A", ">=1.0", []*DefaultPackage{}, []*DefaultPackage{})
a.AddAnnotation("project1", "test1")
a.AddAnnotation("label2", "value1")
b := NewPackage("B", "1.0", []*DefaultPackage{}, []*DefaultPackage{})
b.AddAnnotation("project2", "test2")
b.AddAnnotation("label2", "value1")
It("Expands correctly", func() {
var err error
definitions := NewInMemoryDatabase(false)
for _, p := range []Package{a, b} {
_, err = definitions.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
re := regexp.MustCompile("project[0-9][=].*")
Expect(err).ToNot(HaveOccurred())
Expect(re).ToNot(BeNil())
Expect(a.HasAnnotation("label2")).To(Equal(true))
Expect(a.HasAnnotation("label3")).To(Equal(false))
Expect(a.HasAnnotation("project1")).To(Equal(true))
Expect(b.HasAnnotation("project2")).To(Equal(true))
Expect(b.HasAnnotation("label2")).To(Equal(true))
Expect(b.MatchAnnotation(re)).To(Equal(true))
Expect(a.MatchAnnotation(re)).To(Equal(true))
})
})
@@ -113,6 +190,93 @@ var _ = Describe("Package", func() {
})
})
Context("revdeps", func() {
a := NewPackage("A", "1.0", []*DefaultPackage{}, []*DefaultPackage{})
b := NewPackage("B", "1.0", []*DefaultPackage{&DefaultPackage{Name: "A", Version: ">=1.0"}}, []*DefaultPackage{})
c := NewPackage("C", "1.1", []*DefaultPackage{&DefaultPackage{Name: "B", Version: ">=1.0"}}, []*DefaultPackage{})
d := NewPackage("D", "0.1", []*DefaultPackage{c}, []*DefaultPackage{})
e := NewPackage("E", "0.1", []*DefaultPackage{c}, []*DefaultPackage{})
It("doesn't resolve selectors", func() {
definitions := NewInMemoryDatabase(false)
for _, p := range []Package{a, b, c, d, e} {
_, err := definitions.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
lst := a.Revdeps(definitions)
Expect(len(lst)).To(Equal(0))
})
})
Context("Expandedrevdeps", func() {
a := NewPackage("A", "1.0", []*DefaultPackage{}, []*DefaultPackage{})
b := NewPackage("B", "1.0", []*DefaultPackage{&DefaultPackage{Name: "A", Version: ">=1.0"}}, []*DefaultPackage{})
c := NewPackage("C", "1.1", []*DefaultPackage{&DefaultPackage{Name: "B", Version: ">=1.0"}}, []*DefaultPackage{})
d := NewPackage("D", "0.1", []*DefaultPackage{c}, []*DefaultPackage{})
e := NewPackage("E", "0.1", []*DefaultPackage{c}, []*DefaultPackage{})
It("Computes correctly", func() {
definitions := NewInMemoryDatabase(false)
for _, p := range []Package{a, b, c, d, e} {
_, err := definitions.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
visited := make(map[string]interface{})
lst := a.ExpandedRevdeps(definitions, visited)
Expect(lst).To(ContainElement(c))
Expect(lst).To(ContainElement(d))
Expect(lst).To(ContainElement(e))
Expect(len(lst)).To(Equal(4))
})
})
Context("Expandedrevdeps", func() {
a := NewPackage("A", "1.0", []*DefaultPackage{}, []*DefaultPackage{})
b := NewPackage("B", "1.0", []*DefaultPackage{&DefaultPackage{Name: "A", Version: ">=1.0"}}, []*DefaultPackage{})
c := NewPackage("C", "1.1", []*DefaultPackage{&DefaultPackage{Name: "B", Version: ">=1.0"}}, []*DefaultPackage{})
d := NewPackage("D", "0.1", []*DefaultPackage{&DefaultPackage{Name: "C", Version: ">=1.0"}}, []*DefaultPackage{})
e := NewPackage("E", "0.1", []*DefaultPackage{&DefaultPackage{Name: "C", Version: ">=1.0"}}, []*DefaultPackage{})
It("Computes correctly", func() {
definitions := NewInMemoryDatabase(false)
for _, p := range []Package{a, b, c, d, e} {
_, err := definitions.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
visited := make(map[string]interface{})
lst := a.ExpandedRevdeps(definitions, visited)
Expect(lst).To(ContainElement(b))
Expect(lst).To(ContainElement(c))
Expect(lst).To(ContainElement(d))
Expect(lst).To(ContainElement(e))
Expect(len(lst)).To(Equal(4))
})
})
Context("Expandedrevdeps", func() {
a := NewPackage("A", "1.0", []*DefaultPackage{}, []*DefaultPackage{})
b := NewPackage("B", "1.0", []*DefaultPackage{&DefaultPackage{Name: "A", Version: ">=1.0"}}, []*DefaultPackage{})
c := NewPackage("C", "1.1", []*DefaultPackage{&DefaultPackage{Name: "B", Version: ">=1.0"}, &DefaultPackage{Name: "A", Version: ">=0"}}, []*DefaultPackage{})
d := NewPackage("D", "0.1", []*DefaultPackage{&DefaultPackage{Name: "C", Version: ">=1.0"}}, []*DefaultPackage{})
e := NewPackage("E", "0.1", []*DefaultPackage{&DefaultPackage{Name: "C", Version: ">=1.0"}}, []*DefaultPackage{})
It("Computes correctly", func() {
definitions := NewInMemoryDatabase(false)
for _, p := range []Package{a, b, c, d, e} {
_, err := definitions.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
visited := make(map[string]interface{})
lst := a.ExpandedRevdeps(definitions, visited)
Expect(lst).To(ContainElement(b))
Expect(lst).To(ContainElement(c))
Expect(lst).To(ContainElement(d))
Expect(lst).To(ContainElement(e))
Expect(len(lst)).To(Equal(4))
})
})
Context("RequiresContains", func() {
a := NewPackage("A", ">=1.0", []*DefaultPackage{}, []*DefaultPackage{})
a1 := NewPackage("A", "1.0", []*DefaultPackage{a}, []*DefaultPackage{})
@@ -150,7 +314,6 @@ var _ = Describe("Package", func() {
Expect(p.GetVersion()).To(Equal(a.GetVersion()))
Expect(p.GetName()).To(Equal(a.GetName()))
Expect(p.Flagged()).To(Equal(a.Flagged()))
Expect(p.GetFingerPrint()).To(Equal(a.GetFingerPrint()))
Expect(len(p.GetConflicts())).To(Equal(len(a.GetConflicts())))
Expect(len(p.GetRequires())).To(Equal(len(a.GetRequires())))
@@ -195,7 +358,7 @@ var _ = Describe("Package", func() {
f, err := a1.BuildFormula(definitions, db)
Expect(err).ToNot(HaveOccurred())
Expect(len(f)).To(Equal(2))
Expect(len(f)).To(Equal(8))
// Expect(f[0].String()).To(Equal("or(not(c31f5842), a4910f77)"))
// Expect(f[1].String()).To(Equal("or(not(c31f5842), not(a97670be))"))
})
@@ -210,7 +373,6 @@ var _ = Describe("Package", func() {
a2 := a.Clone()
Expect(a2.GetVersion()).To(Equal(a.GetVersion()))
Expect(a2.GetName()).To(Equal(a.GetName()))
Expect(a2.Flagged()).To(Equal(a.Flagged()))
Expect(a2.GetFingerPrint()).To(Equal(a.GetFingerPrint()))
Expect(len(a2.GetConflicts())).To(Equal(len(a.GetConflicts())))
Expect(len(a2.GetRequires())).To(Equal(len(a.GetRequires())))
@@ -236,4 +398,69 @@ var _ = Describe("Package", func() {
Expect(len(a1.GetUses())).To(Equal(0))
})
})
Context("Check Bump build Version", func() {
It("Bump without build version", func() {
a1 := NewPackage("A", "1.0", []*DefaultPackage{}, []*DefaultPackage{})
err := a1.BumpBuildVersion()
Expect(err).ToNot(HaveOccurred())
Expect(a1.GetVersion()).To(Equal("1.0+1"))
})
It("Bump 2", func() {
p := NewPackage("A", "1.0+1", []*DefaultPackage{}, []*DefaultPackage{})
err := p.BumpBuildVersion()
Expect(err).ToNot(HaveOccurred())
Expect(p.GetVersion()).To(Equal("1.0+2"))
})
It("Bump 3", func() {
p := NewPackage("A", "1.0+100", []*DefaultPackage{}, []*DefaultPackage{})
err := p.BumpBuildVersion()
Expect(err).ToNot(HaveOccurred())
Expect(p.GetVersion()).To(Equal("1.0+101"))
})
It("Bump 4", func() {
p := NewPackage("A", "1.0+r1", []*DefaultPackage{}, []*DefaultPackage{})
err := p.BumpBuildVersion()
Expect(err).ToNot(HaveOccurred())
Expect(p.GetVersion()).To(Equal("1.0+r2"))
})
It("Bump 5", func() {
p := NewPackage("A", "1.0+p1", []*DefaultPackage{}, []*DefaultPackage{})
err := p.BumpBuildVersion()
Expect(err).ToNot(HaveOccurred())
Expect(p.GetVersion()).To(Equal("1.0+p2"))
})
It("Bump 6", func() {
p := NewPackage("A", "1.0+pre20200315", []*DefaultPackage{}, []*DefaultPackage{})
err := p.BumpBuildVersion()
Expect(err).ToNot(HaveOccurred())
Expect(p.GetVersion()).To(Equal("1.0+pre20200315.1"))
})
It("Bump 7", func() {
p := NewPackage("A", "1.0+pre20200315.1", []*DefaultPackage{}, []*DefaultPackage{})
err := p.BumpBuildVersion()
Expect(err).ToNot(HaveOccurred())
Expect(p.GetVersion()).To(Equal("1.0+pre20200315.2"))
})
It("Bump 8", func() {
p := NewPackage("A", "1.0+d-r1", []*DefaultPackage{}, []*DefaultPackage{})
err := p.BumpBuildVersion()
Expect(err).ToNot(HaveOccurred())
Expect(p.GetVersion()).To(Equal("1.0+d-r1.1"))
})
It("Bump 9", func() {
p := NewPackage("A", "1.0+p20200315.1", []*DefaultPackage{}, []*DefaultPackage{})
err := p.BumpBuildVersion()
Expect(err).ToNot(HaveOccurred())
Expect(p.GetVersion()).To(Equal("1.0+p20200315.2"))
})
})
})

85
pkg/repository/loader.go Normal file
View File

@@ -0,0 +1,85 @@
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package repository
import (
"io/ioutil"
"path"
"regexp"
"github.com/ghodss/yaml"
. "github.com/mudler/luet/pkg/config"
. "github.com/mudler/luet/pkg/logger"
)
func LoadRepositories(c *LuetConfig) error {
var regexRepo = regexp.MustCompile(`.yml$|.yaml$`)
for _, rdir := range c.RepositoriesConfDir {
Debug("Parsing Repository Directory", rdir, "...")
files, err := ioutil.ReadDir(rdir)
if err != nil {
Debug("Skip dir", rdir, ":", err.Error())
continue
}
for _, file := range files {
if file.IsDir() {
continue
}
if !regexRepo.MatchString(file.Name()) {
Debug("File", file.Name(), "skipped.")
continue
}
content, err := ioutil.ReadFile(path.Join(rdir, file.Name()))
if err != nil {
Warning("On read file", file.Name(), ":", err.Error())
Warning("File", file.Name(), "skipped.")
continue
}
r, err := LoadRepository(content)
if err != nil {
Warning("On parse file", file.Name(), ":", err.Error())
Warning("File", file.Name(), "skipped.")
continue
}
if r.Name == "" || len(r.Urls) == 0 || r.Type == "" {
Warning("Invalid repository ", file.Name())
Warning("File", file.Name(), "skipped.")
continue
}
c.AddSystemRepository(*r)
}
}
return nil
}
func LoadRepository(data []byte) (*LuetRepository, error) {
ans := NewEmptyLuetRepository()
err := yaml.Unmarshal(data, &ans)
if err != nil {
return nil, err
}
return ans, nil
}

View File

@@ -0,0 +1,33 @@
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package repository_test
import (
"testing"
. "github.com/mudler/luet/cmd"
config "github.com/mudler/luet/pkg/config"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestSolver(t *testing.T) {
RegisterFailHandler(Fail)
LoadConfig(config.LuetCfg)
RunSpecs(t, "Repository Suite")
}

View File

@@ -0,0 +1,56 @@
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
// Daniele Rondina <geaaru@sabayonlinux.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package repository_test
import (
. "github.com/mudler/luet/pkg/config"
. "github.com/mudler/luet/pkg/repository"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
viper "github.com/spf13/viper"
)
var _ = Describe("Repository", func() {
Context("Load Repository1", func() {
cfg := NewLuetConfig(viper.New())
cfg.RepositoriesConfDir = []string{
"../../tests/fixtures/repos.conf.d",
}
err := LoadRepositories(cfg)
It("Chec Load Repository 1", func() {
Expect(err).Should(BeNil())
Expect(len(cfg.SystemRepositories)).Should(Equal(2))
Expect(cfg.SystemRepositories[0].Name).Should(Equal("test1"))
Expect(cfg.SystemRepositories[0].Priority).Should(Equal(999))
Expect(cfg.SystemRepositories[0].Type).Should(Equal("disk"))
Expect(len(cfg.SystemRepositories[0].Urls)).Should(Equal(1))
Expect(cfg.SystemRepositories[0].Urls[0]).Should(Equal("tests/repos/test1"))
})
It("Chec Load Repository 2", func() {
Expect(err).Should(BeNil())
Expect(len(cfg.SystemRepositories)).Should(Equal(2))
Expect(cfg.SystemRepositories[1].Name).Should(Equal("test2"))
Expect(cfg.SystemRepositories[1].Priority).Should(Equal(1000))
Expect(cfg.SystemRepositories[1].Type).Should(Equal("disk"))
Expect(len(cfg.SystemRepositories[1].Urls)).Should(Equal(1))
Expect(cfg.SystemRepositories[1].Urls[0]).Should(Equal("tests/repos/test2"))
})
})
})

View File

@@ -23,6 +23,7 @@ import (
pkg "github.com/mudler/luet/pkg/package"
toposort "github.com/philopon/go-toposort"
"github.com/pkg/errors"
"github.com/stevenle/topsort"
)
@@ -145,7 +146,7 @@ func (assertions PackagesAssertions) Search(f string) *PackageAssert {
return nil
}
func (assertions PackagesAssertions) Order(definitiondb pkg.PackageDatabase, fingerprint string) PackagesAssertions {
func (assertions PackagesAssertions) Order(definitiondb pkg.PackageDatabase, fingerprint string) (PackagesAssertions, error) {
orderedAssertions := PackagesAssertions{}
unorderedAssertions := PackagesAssertions{}
@@ -169,10 +170,10 @@ func (assertions PackagesAssertions) Order(definitiondb pkg.PackageDatabase, fin
sort.Sort(unorderedAssertions)
// Build a topological graph
//graph := toposort.NewGraph(len(unorderedAssertions))
// graph.AddNodes(fingerprints...)
for _, a := range unorderedAssertions {
currentPkg := a.Package
added := map[string]interface{}{}
REQUIRES:
for _, requiredDef := range currentPkg.GetRequires() {
if def, err := definitiondb.FindPackage(requiredDef); err == nil { // Provides: Get a chance of being override here
requiredDef = def.(*pkg.DefaultPackage)
@@ -184,26 +185,29 @@ func (assertions PackagesAssertions) Order(definitiondb pkg.PackageDatabase, fin
if req != nil {
requiredDef = req.Package
}
if _, ok := added[requiredDef.GetFingerPrint()]; ok {
continue REQUIRES
}
// Expand also here, as we need to order them (or instead the solver should give back the dep correctly?)
graph.AddEdge(currentPkg.GetFingerPrint(), requiredDef.GetFingerPrint())
added[requiredDef.GetFingerPrint()] = nil
}
}
result, err := graph.TopSort(fingerprint)
if err != nil {
panic(err)
return nil, errors.Wrap(err, "fail on sorting "+fingerprint)
}
for _, res := range result {
a, ok := tmpMap[res]
if !ok {
panic("fail")
return nil, errors.New("fail looking for " + res)
// continue
}
orderedAssertions = append(orderedAssertions, a)
// orderedAssertions = append(PackagesAssertions{a}, orderedAssertions...) // push upfront
}
//helpers.ReverseAny(orderedAssertions)
return orderedAssertions
return orderedAssertions, nil
}
func (assertions PackagesAssertions) Explain() string {
@@ -247,6 +251,34 @@ func (a PackagesAssertions) Less(i, j int) bool {
}
func (a PackagesAssertions) TrueLen() int {
count := 0
for _, ass := range a {
if ass.Value {
count++
}
}
return count
}
// HashFrom computes the assertion hash From a given package. It drops it from the assertions
// and checks it's not the only one. if it's unique it marks it specially - so the hash
// which is generated is unique for the selected package
func (assertions PackagesAssertions) HashFrom(p pkg.Package) string {
var assertionhash string
// When we don't have any solution to hash for, we need to generate an UUID by ourselves
latestsolution := assertions.Drop(p)
if latestsolution.TrueLen() == 0 {
assertionhash = assertions.Mark(p).AssertionHash()
} else {
assertionhash = latestsolution.AssertionHash()
}
return assertionhash
}
func (assertions PackagesAssertions) AssertionHash() string {
var fingerprint string
for _, assertion := range assertions { // Note: Always order them first!
@@ -283,3 +315,18 @@ func (assertions PackagesAssertions) Cut(p pkg.Package) PackagesAssertions {
}
return ass
}
// Mark returns a new assertion with the package marked
func (assertions PackagesAssertions) Mark(p pkg.Package) PackagesAssertions {
ass := PackagesAssertions{}
for _, a := range assertions {
if a.Package.Matches(p) {
marked := a.Package.Clone()
marked.SetName("@@" + marked.GetName())
a = PackageAssert{Package: marked.(*pkg.DefaultPackage), Value: a.Value, Hash: a.Hash}
}
ass = append(ass, a)
}
return ass
}

View File

@@ -19,6 +19,7 @@ import (
"strconv"
pkg "github.com/mudler/luet/pkg/package"
"github.com/mudler/luet/pkg/solver"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
@@ -72,7 +73,8 @@ var _ = Describe("Decoder", func() {
Expect(len(solution)).To(Equal(6))
Expect(err).ToNot(HaveOccurred())
solution = solution.Order(dbDefinitions, A.GetFingerPrint())
solution, err = solution.Order(dbDefinitions, A.GetFingerPrint())
Expect(err).ToNot(HaveOccurred())
// Expect(len(solution)).To(Equal(6))
Expect(solution[0].Package.GetName()).To(Equal("G"))
Expect(solution[1].Package.GetName()).To(Equal("H"))
@@ -188,7 +190,8 @@ var _ = Describe("Decoder", func() {
Expect(len(solution)).To(Equal(6))
Expect(err).ToNot(HaveOccurred())
solution = solution.Order(dbDefinitions, A.GetFingerPrint())
solution, err = solution.Order(dbDefinitions, A.GetFingerPrint())
Expect(err).ToNot(HaveOccurred())
// Expect(len(solution)).To(Equal(6))
Expect(solution[0].Package.GetName()).To(Equal("G"))
Expect(solution[1].Package.GetName()).To(Equal("H"))
@@ -206,7 +209,8 @@ var _ = Describe("Decoder", func() {
Expect(len(solution)).To(Equal(6))
Expect(err).ToNot(HaveOccurred())
solution = solution.Order(dbDefinitions, B.GetFingerPrint())
solution, err = solution.Order(dbDefinitions, B.GetFingerPrint())
Expect(err).ToNot(HaveOccurred())
hash2 := solution.AssertionHash()
// Expect(len(solution)).To(Equal(6))
@@ -243,7 +247,11 @@ var _ = Describe("Decoder", func() {
solution2, err := s.Install([]pkg.Package{Z})
Expect(err).ToNot(HaveOccurred())
Expect(solution.Order(dbDefinitions, Y.GetFingerPrint()).Drop(Y).AssertionHash() == solution2.Order(dbDefinitions, Z.GetFingerPrint()).Drop(Z).AssertionHash()).To(BeTrue())
orderY, err := solution.Order(dbDefinitions, Y.GetFingerPrint())
Expect(err).ToNot(HaveOccurred())
orderZ, err := solution2.Order(dbDefinitions, Z.GetFingerPrint())
Expect(err).ToNot(HaveOccurred())
Expect(orderY.Drop(Y).AssertionHash() == orderZ.Drop(Z).AssertionHash()).To(BeTrue())
})
It("Hashes them, Cuts them and could be used for comparison", func() {
@@ -267,10 +275,120 @@ var _ = Describe("Decoder", func() {
solution2, err := s.Install([]pkg.Package{Z})
Expect(err).ToNot(HaveOccurred())
Expect(solution.Order(dbDefinitions, Y.GetFingerPrint()).Cut(Y).Drop(Y)).To(Equal(solution2.Order(dbDefinitions, Z.GetFingerPrint()).Cut(Z).Drop(Z)))
orderY, err := solution.Order(dbDefinitions, Y.GetFingerPrint())
Expect(err).ToNot(HaveOccurred())
orderZ, err := solution2.Order(dbDefinitions, Z.GetFingerPrint())
Expect(err).ToNot(HaveOccurred())
Expect(orderY.Cut(Y).Drop(Y)).To(Equal(orderZ.Cut(Z).Drop(Z)))
Expect(solution.Order(dbDefinitions, Y.GetFingerPrint()).Cut(Y).Drop(Y).AssertionHash()).To(Equal(solution2.Order(dbDefinitions, Z.GetFingerPrint()).Cut(Z).Drop(Z).AssertionHash()))
Expect(orderY.Cut(Y).Drop(Y).AssertionHash()).To(Equal(orderZ.Cut(Z).Drop(Z).AssertionHash()))
})
It("HashFrom can be used equally", func() {
X := pkg.NewPackage("X", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
Y := pkg.NewPackage("Y", "", []*pkg.DefaultPackage{X}, []*pkg.DefaultPackage{})
Z := pkg.NewPackage("Z", "", []*pkg.DefaultPackage{X}, []*pkg.DefaultPackage{})
for _, p := range []pkg.Package{X, Y, Z} {
_, err := dbDefinitions.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
for _, p := range []pkg.Package{} {
_, err := dbInstalled.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
solution, err := s.Install([]pkg.Package{Y})
Expect(err).ToNot(HaveOccurred())
solution2, err := s.Install([]pkg.Package{Z})
Expect(err).ToNot(HaveOccurred())
orderY, err := solution.Order(dbDefinitions, Y.GetFingerPrint())
Expect(err).ToNot(HaveOccurred())
orderZ, err := solution2.Order(dbDefinitions, Z.GetFingerPrint())
Expect(err).ToNot(HaveOccurred())
Expect(orderY.Cut(Y).Drop(Y)).To(Equal(orderZ.Cut(Z).Drop(Z)))
Expect(orderY.Cut(Y).HashFrom(Y)).To(Equal(orderZ.Cut(Z).HashFrom(Z)))
})
It("Unique hashes for single packages", func() {
X := pkg.NewPackage("X", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
F := pkg.NewPackage("F", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
D := pkg.NewPackage("X", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
for _, p := range []pkg.Package{X, F, D} {
_, err := dbDefinitions.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
for _, p := range []pkg.Package{} {
_, err := dbInstalled.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
solution, err := s.Install([]pkg.Package{X})
Expect(err).ToNot(HaveOccurred())
solution2, err := s.Install([]pkg.Package{F})
Expect(err).ToNot(HaveOccurred())
solution3, err := s.Install([]pkg.Package{D})
Expect(err).ToNot(HaveOccurred())
Expect(solution.AssertionHash()).ToNot(Equal(solution2.AssertionHash()))
Expect(solution3.AssertionHash()).To(Equal(solution.AssertionHash()))
})
It("Unique hashes for empty assertions", func() {
empty := solver.PackagesAssertions{}
empty2 := solver.PackagesAssertions{}
Expect(empty.AssertionHash()).To(Equal(empty2.AssertionHash()))
})
It("Unique hashes for single packages with HashFrom", func() {
X := pkg.NewPackage("X", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
F := pkg.NewPackage("F", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
D := pkg.NewPackage("X", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
Y := pkg.NewPackage("Y", "", []*pkg.DefaultPackage{X}, []*pkg.DefaultPackage{})
empty := solver.PackagesAssertions{}
for _, p := range []pkg.Package{X, F, D, Y} {
_, err := dbDefinitions.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
for _, p := range []pkg.Package{} {
_, err := dbInstalled.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
solution, err := s.Install([]pkg.Package{X})
Expect(err).ToNot(HaveOccurred())
solution2, err := s.Install([]pkg.Package{F})
Expect(err).ToNot(HaveOccurred())
solution3, err := s.Install([]pkg.Package{D})
Expect(err).ToNot(HaveOccurred())
solution4, err := s.Install([]pkg.Package{Y})
Expect(err).ToNot(HaveOccurred())
Expect(solution.HashFrom(X)).ToNot(Equal(solution2.HashFrom(F)))
Expect(solution3.HashFrom(D)).To(Equal(solution.HashFrom(X)))
Expect(empty.AssertionHash()).ToNot(Equal(solution3.HashFrom(D)))
Expect(empty.AssertionHash()).ToNot(Equal(solution2.HashFrom(F)))
Expect(solution4.Drop(Y).AssertionHash()).To(Equal(solution4.HashFrom(Y)))
})
})
})

336
pkg/solver/resolver.go Normal file
View File

@@ -0,0 +1,336 @@
// Copyright © 2020 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package solver
import (
"encoding/json"
"fmt"
"strconv"
"github.com/crillab/gophersat/bf"
"github.com/mudler/luet/pkg/helpers"
"gopkg.in/yaml.v2"
"github.com/ecooper/qlearning"
pkg "github.com/mudler/luet/pkg/package"
"github.com/pkg/errors"
)
type ActionType int
const (
NoAction = 0
Solved = iota
NoSolution = iota
Going = iota
ActionRemoved = iota
ActionAdded = iota
DoNoop = false
ActionDomains = 3 // Bump it if you increase the number of actions
DefaultMaxAttempts = 9000
DefaultLearningRate = 0.7
DefaultDiscount = 1.0
DefaultInitialObserved = 999999
QLearningResolverType = "qlearning"
)
//. "github.com/mudler/luet/pkg/logger"
// PackageResolver assists PackageSolver on unsat cases
type PackageResolver interface {
Solve(bf.Formula, PackageSolver) (PackagesAssertions, error)
}
type DummyPackageResolver struct {
}
func (*DummyPackageResolver) Solve(bf.Formula, PackageSolver) (PackagesAssertions, error) {
return nil, errors.New("Could not satisfy the constraints. Try again by removing deps ")
}
type QLearningResolver struct {
Attempts int
ToAttempt int
attempts int
Attempted map[string]bool
Solver PackageSolver
Formula bf.Formula
Targets pkg.Packages
Current pkg.Packages
observedDelta int
observedDeltaChoice pkg.Packages
Agent *qlearning.SimpleAgent
}
func SimpleQLearningSolver() PackageResolver {
return NewQLearningResolver(DefaultLearningRate, DefaultDiscount, DefaultMaxAttempts, DefaultInitialObserved)
}
// Defaults LearningRate 0.7, Discount 1.0
func NewQLearningResolver(LearningRate, Discount float32, MaxAttempts, initialObservedDelta int) PackageResolver {
return &QLearningResolver{
Agent: qlearning.NewSimpleAgent(LearningRate, Discount),
observedDelta: initialObservedDelta,
Attempts: MaxAttempts,
}
}
func (resolver *QLearningResolver) Solve(f bf.Formula, s PackageSolver) (PackagesAssertions, error) {
// Info("Using QLearning solver to resolve conflicts. Please be patient.")
resolver.Solver = s
s.SetResolver(&DummyPackageResolver{}) // Set dummy. Otherwise the attempts will run again a QLearning instance.
defer s.SetResolver(resolver) // Set back ourselves as resolver
resolver.Formula = f
// Our agent by default has a learning rate of 0.7 and discount of 1.0.
if resolver.Agent == nil {
resolver.Agent = qlearning.NewSimpleAgent(DefaultLearningRate, DefaultDiscount) // FIXME: Remove hardcoded values
}
// 3 are the action domains, counting noop regardless if enabled or not
// get the permutations to attempt
resolver.ToAttempt = int(helpers.Factorial(uint64(len(resolver.Solver.(*Solver).Wanted)-1) * ActionDomains)) // TODO: type assertions must go away
resolver.Targets = resolver.Solver.(*Solver).Wanted
resolver.attempts = resolver.Attempts
resolver.Attempted = make(map[string]bool, len(resolver.Targets))
for resolver.IsComplete() == Going {
// Pick the next move, which is going to be a letter choice.
action := qlearning.Next(resolver.Agent, resolver)
// Whatever that choice is, let's update our model for its
// impact. If the package chosen makes the formula sat,
// then this action will be positive. Otherwise, it will be
// negative.
resolver.Agent.Learn(action, resolver)
// Reward doesn't change state so we can check what the
// reward would be for this action, and report how the
// env changed.
// score := resolver.Reward(action)
// if score > 0.0 {
// resolver.Log("%s was correct", action.Action.String())
// } else {
// resolver.Log("%s was incorrect", action.Action.String())
// }
}
// If we get good result, take it
// Take the result also if we did reached overall maximum attempts
if resolver.IsComplete() == Solved || resolver.IsComplete() == NoSolution {
if len(resolver.observedDeltaChoice) != 0 {
// Take the minimum delta observed choice result, and consume it (Try sets the wanted list)
resolver.Solver.(*Solver).Wanted = resolver.observedDeltaChoice
}
return resolver.Solver.Solve()
} else {
return nil, errors.New("QLearning resolver failed ")
}
}
// Returns the current state.
func (resolver *QLearningResolver) IsComplete() int {
if resolver.attempts < 1 {
return NoSolution
}
if resolver.ToAttempt > 0 {
return Going
}
return Solved
}
func (resolver *QLearningResolver) Try(c Choice) error {
pack := c.Package
packtoAdd := pkg.FromString(pack)
resolver.Attempted[pack+strconv.Itoa(int(c.Action))] = true // increase the count
s, _ := resolver.Solver.(*Solver)
var filtered pkg.Packages
switch c.Action {
case ActionAdded:
found := false
for _, p := range s.Wanted {
if p.String() == pack {
found = true
break
}
}
if !found {
resolver.Solver.(*Solver).Wanted = append(resolver.Solver.(*Solver).Wanted, packtoAdd)
}
case ActionRemoved:
for _, p := range s.Wanted {
if p.String() != pack {
filtered = append(filtered, p)
}
}
resolver.Solver.(*Solver).Wanted = filtered
}
_, err := resolver.Solver.Solve()
return err
}
// Choose applies a pack attempt, returning
// true if the formula returns sat.
//
// Choose updates the resolver's state.
func (resolver *QLearningResolver) Choose(c Choice) bool {
//pack := pkg.FromString(c.Package)
err := resolver.Try(c)
if err == nil {
resolver.ToAttempt--
resolver.attempts-- // Decrease attempts - it's a barrier. We could also do not decrease it here, allowing more attempts to be made
} else {
resolver.attempts--
return false
}
return true
}
// Reward returns a score for a given qlearning.StateAction. Reward is a
// member of the qlearning.Rewarder interface. If the choice will make sat the formula, a positive score is returned.
// Otherwise, a static -1000 is returned.
func (resolver *QLearningResolver) Reward(action *qlearning.StateAction) float32 {
choice := action.Action.(*Choice)
//_, err := resolver.Solver.Solve()
err := resolver.Try(*choice)
toBeInstalled := len(resolver.Solver.(*Solver).Wanted)
originalTarget := len(resolver.Targets)
noaction := choice.Action == NoAction
delta := originalTarget - toBeInstalled
if err == nil {
// if toBeInstalled == originalTarget { // Base case: all the targets matches (it shouldn't happen, but lets put a higher)
// Debug("Target match, maximum score")
// return 24.0 / float32(len(resolver.Attempted))
// }
if DoNoop {
if noaction && toBeInstalled == 0 { // We decided to stay in the current state, and no targets have been chosen
return -100
}
}
if delta <= resolver.observedDelta { // Try to maximise observedDelta
resolver.observedDelta = delta
resolver.observedDeltaChoice = resolver.Solver.(*Solver).Wanted // we store it as this is our return value at the end
return 24.0 / float32(len(resolver.Attempted))
} else if toBeInstalled > 0 { // If we installed something, at least give a good score
return 24.0 / float32(len(resolver.Attempted))
}
}
return -1000
}
// Next creates a new slice of qlearning.Action instances. A possible
// action is created for each package that could be removed from the formula's target
func (resolver *QLearningResolver) Next() []qlearning.Action {
actions := make([]qlearning.Action, 0, (len(resolver.Targets)-1)*3)
TARGETS:
for _, pack := range resolver.Targets {
for _, current := range resolver.Solver.(*Solver).Wanted {
if current.String() == pack.String() {
actions = append(actions, &Choice{Package: pack.String(), Action: ActionRemoved})
continue TARGETS
}
}
actions = append(actions, &Choice{Package: pack.String(), Action: ActionAdded})
}
if DoNoop {
actions = append(actions, &Choice{Package: "", Action: NoAction}) // NOOP
}
return actions
}
// Log is a wrapper of fmt.Printf. If Game.debug is true, Log will print
// to stdout.
func (resolver *QLearningResolver) Log(msg string, args ...interface{}) {
logMsg := fmt.Sprintf("(%d moves, %d remaining attempts) %s\n", len(resolver.Attempted), resolver.attempts, msg)
fmt.Println(fmt.Sprintf(logMsg, args...))
}
// String returns a consistent hash for the current env state to be
// used in a qlearning.Agent.
func (resolver *QLearningResolver) String() string {
return fmt.Sprintf("%v", resolver.Solver.(*Solver).Wanted)
}
// Choice implements qlearning.Action for a package choice for removal from wanted targets
type Choice struct {
Package string `json:"pack"`
Action ActionType `json:"action"`
}
func ChoiceFromString(s string) (*Choice, error) {
var p *Choice
err := yaml.Unmarshal([]byte(s), &p)
if err != nil {
return nil, err
}
return p, nil
}
// String returns the character for the current action.
func (choice *Choice) String() string {
data, err := json.Marshal(choice)
if err != nil {
return ""
}
return string(data)
}
// Apply updates the state of the solver for the package choice.
func (choice *Choice) Apply(state qlearning.State) qlearning.State {
resolver := state.(*QLearningResolver)
resolver.Choose(*choice)
return resolver
}

182
pkg/solver/resolver_test.go Normal file
View File

@@ -0,0 +1,182 @@
// Copyright © 2019 Ettore Di Giacinto <mudler@gentoo.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see <http://www.gnu.org/licenses/>.
package solver_test
import (
pkg "github.com/mudler/luet/pkg/package"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/mudler/luet/pkg/solver"
)
var _ = Describe("Resolver", func() {
db := pkg.NewInMemoryDatabase(false)
dbInstalled := pkg.NewInMemoryDatabase(false)
dbDefinitions := pkg.NewInMemoryDatabase(false)
s := NewSolver(dbInstalled, dbDefinitions, db)
BeforeEach(func() {
db = pkg.NewInMemoryDatabase(false)
dbInstalled = pkg.NewInMemoryDatabase(false)
dbDefinitions = pkg.NewInMemoryDatabase(false)
s = NewSolver(dbInstalled, dbDefinitions, db)
})
Context("Conflict set", func() {
Context("DummyPackageResolver", func() {
It("is unsolvable - as we something we ask to install conflict with system stuff", func() {
C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{C})
A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{})
for _, p := range []pkg.Package{A, B, C} {
_, err := dbDefinitions.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
for _, p := range []pkg.Package{C} {
_, err := dbInstalled.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
solution, err := s.Install([]pkg.Package{A})
Expect(len(solution)).To(Equal(0))
Expect(err).To(HaveOccurred())
})
It("succeeds to install D and F if explictly requested", func() {
C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{C})
A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{})
D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
E := pkg.NewPackage("E", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{})
F := pkg.NewPackage("F", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
for _, p := range []pkg.Package{A, B, C, D, E, F} {
_, err := dbDefinitions.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
for _, p := range []pkg.Package{C} {
_, err := dbInstalled.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
solution, err := s.Install([]pkg.Package{D, F}) // D and F should go as they have no deps. A/E should be filtered by QLearn
Expect(err).ToNot(HaveOccurred())
Expect(len(solution)).To(Equal(6))
Expect(solution).To(ContainElement(PackageAssert{Package: A, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: B, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: D, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: E, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: F, Value: true}))
})
})
Context("QLearningResolver", func() {
It("will find out that we can install D by ignoring A", func() {
s.SetResolver(SimpleQLearningSolver())
C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{C})
A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{})
D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
for _, p := range []pkg.Package{A, B, C, D} {
_, err := dbDefinitions.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
for _, p := range []pkg.Package{C} {
_, err := dbInstalled.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
solution, err := s.Install([]pkg.Package{A, D})
Expect(err).ToNot(HaveOccurred())
Expect(solution).To(ContainElement(PackageAssert{Package: A, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: B, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: D, Value: true}))
Expect(len(solution)).To(Equal(4))
})
It("will find out that we can install D and F by ignoring E and A", func() {
s.SetResolver(SimpleQLearningSolver())
C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{C})
A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{})
D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
E := pkg.NewPackage("E", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{})
F := pkg.NewPackage("F", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
for _, p := range []pkg.Package{A, B, C, D, E, F} {
_, err := dbDefinitions.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
for _, p := range []pkg.Package{C} {
_, err := dbInstalled.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
solution, err := s.Install([]pkg.Package{A, D, E, F}) // D and F should go as they have no deps. A/E should be filtered by QLearn
Expect(err).ToNot(HaveOccurred())
Expect(solution).To(ContainElement(PackageAssert{Package: A, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: B, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: C, Value: true})) // Was already installed
Expect(solution).To(ContainElement(PackageAssert{Package: D, Value: true}))
Expect(solution).To(ContainElement(PackageAssert{Package: E, Value: false}))
Expect(solution).To(ContainElement(PackageAssert{Package: F, Value: true}))
Expect(len(solution)).To(Equal(6))
})
})
Context("DummyPackageResolver", func() {
It("cannot find a solution", func() {
C := pkg.NewPackage("C", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
B := pkg.NewPackage("B", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{C})
A := pkg.NewPackage("A", "", []*pkg.DefaultPackage{B}, []*pkg.DefaultPackage{})
D := pkg.NewPackage("D", "", []*pkg.DefaultPackage{}, []*pkg.DefaultPackage{})
for _, p := range []pkg.Package{A, B, C, D} {
_, err := dbDefinitions.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
for _, p := range []pkg.Package{C} {
_, err := dbInstalled.CreatePackage(p)
Expect(err).ToNot(HaveOccurred())
}
solution, err := s.Install([]pkg.Package{A, D})
Expect(err).To(HaveOccurred())
Expect(len(solution)).To(Equal(0))
})
})
})
})

View File

@@ -18,6 +18,8 @@ package solver
import (
//. "github.com/mudler/luet/pkg/logger"
"fmt"
"github.com/pkg/errors"
"github.com/crillab/gophersat/bf"
@@ -27,39 +29,61 @@ import (
// PackageSolver is an interface to a generic package solving algorithm
type PackageSolver interface {
SetDefinitionDatabase(pkg.PackageDatabase)
Install(p []pkg.Package) (PackagesAssertions, error)
Uninstall(candidate pkg.Package) ([]pkg.Package, error)
Install(p pkg.Packages) (PackagesAssertions, error)
Uninstall(candidate pkg.Package, checkconflicts, full bool) (pkg.Packages, error)
ConflictsWithInstalled(p pkg.Package) (bool, error)
ConflictsWith(p pkg.Package, ls []pkg.Package) (bool, error)
World() []pkg.Package
Upgrade() ([]pkg.Package, PackagesAssertions, error)
ConflictsWith(p pkg.Package, ls pkg.Packages) (bool, error)
Conflicts(pack pkg.Package, lsp pkg.Packages) (bool, error)
World() pkg.Packages
Upgrade(checkconflicts, full bool) (pkg.Packages, PackagesAssertions, error)
UpgradeUniverse(dropremoved bool) (pkg.Packages, PackagesAssertions, error)
UninstallUniverse(toremove pkg.Packages) (pkg.Packages, error)
SetResolver(PackageResolver)
Solve() (PackagesAssertions, error)
}
// Solver is the default solver for luet
type Solver struct {
DefinitionDatabase pkg.PackageDatabase
SolverDatabase pkg.PackageDatabase
Wanted []pkg.Package
Wanted pkg.Packages
InstalledDatabase pkg.PackageDatabase
Resolver PackageResolver
}
// NewSolver accepts as argument two lists of packages, the first is the initial set,
// the second represent all the known packages.
func NewSolver(installed pkg.PackageDatabase, definitiondb pkg.PackageDatabase, solverdb pkg.PackageDatabase) PackageSolver {
return &Solver{InstalledDatabase: installed, DefinitionDatabase: definitiondb, SolverDatabase: solverdb}
return NewResolver(installed, definitiondb, solverdb, &DummyPackageResolver{})
}
// SetWorld is a setter for the list of all known packages to the solver
// NewReSolver accepts as argument two lists of packages, the first is the initial set,
// the second represent all the known packages.
func NewResolver(installed pkg.PackageDatabase, definitiondb pkg.PackageDatabase, solverdb pkg.PackageDatabase, re PackageResolver) PackageSolver {
return &Solver{InstalledDatabase: installed, DefinitionDatabase: definitiondb, SolverDatabase: solverdb, Resolver: re}
}
// SetDefinitionDatabase is a setter for the definition Database
func (s *Solver) SetDefinitionDatabase(db pkg.PackageDatabase) {
s.DefinitionDatabase = db
}
func (s *Solver) World() []pkg.Package {
// SetResolver is a setter for the unsat resolver backend
func (s *Solver) SetResolver(r PackageResolver) {
s.Resolver = r
}
func (s *Solver) World() pkg.Packages {
return s.DefinitionDatabase.World()
}
func (s *Solver) Installed() []pkg.Package {
func (s *Solver) Installed() pkg.Packages {
return s.InstalledDatabase.World()
}
@@ -74,6 +98,16 @@ func (s *Solver) noRulesWorld() bool {
return true
}
func (s *Solver) noRulesInstalled() bool {
for _, p := range s.Installed() {
if len(p.GetConflicts()) != 0 || len(p.GetRequires()) != 0 {
return false
}
}
return true
}
func (s *Solver) BuildInstalled() (bf.Formula, error) {
var formulas []bf.Formula
for _, p := range s.Installed() {
@@ -113,8 +147,8 @@ func (s *Solver) BuildWorld(includeInstalled bool) (bf.Formula, error) {
return bf.And(formulas...), nil
}
func (s *Solver) getList(db pkg.PackageDatabase, lsp []pkg.Package) ([]pkg.Package, error) {
var ls []pkg.Package
func (s *Solver) getList(db pkg.PackageDatabase, lsp pkg.Packages) (pkg.Packages, error) {
var ls pkg.Packages
for _, pp := range lsp {
cp, err := db.FindPackage(pp)
@@ -124,7 +158,7 @@ func (s *Solver) getList(db pkg.PackageDatabase, lsp []pkg.Package) ([]pkg.Packa
if err != nil || len(packages) == 0 {
cp = pp
} else {
cp = pkg.Best(packages)
cp = packages.Best(nil)
}
}
ls = append(ls, cp)
@@ -132,7 +166,44 @@ func (s *Solver) getList(db pkg.PackageDatabase, lsp []pkg.Package) ([]pkg.Packa
return ls, nil
}
func (s *Solver) ConflictsWith(pack pkg.Package, lsp []pkg.Package) (bool, error) {
// Conflicts acts like ConflictsWith, but uses package's reverse dependencies to
// determine if it conflicts with the given set
func (s *Solver) Conflicts(pack pkg.Package, lsp pkg.Packages) (bool, error) {
p, err := s.DefinitionDatabase.FindPackage(pack)
if err != nil {
p = pack
}
ls, err := s.getList(s.DefinitionDatabase, lsp)
if err != nil {
return false, errors.Wrap(err, "Package not found in definition db")
}
if s.noRulesWorld() {
return false, nil
}
temporarySet := pkg.NewInMemoryDatabase(false)
for _, p := range ls {
temporarySet.CreatePackage(p)
}
visited := make(map[string]interface{})
revdeps := p.ExpandedRevdeps(temporarySet, visited)
var revdepsErr error
for _, r := range revdeps {
if revdepsErr == nil {
revdepsErr = errors.New("")
}
revdepsErr = errors.New(fmt.Sprintf("%s\n%s", revdepsErr.Error(), r.HumanReadableString()))
}
return len(revdeps) != 0, revdepsErr
}
// ConflictsWith return true if a package is part of the requirement set of a list of package
// return false otherwise (and thus it is NOT relevant to the given list)
func (s *Solver) ConflictsWith(pack pkg.Package, lsp pkg.Packages) (bool, error) {
p, err := s.DefinitionDatabase.FindPackage(pack)
if err != nil {
p = pack //Relax search, otherwise we cannot compute solutions for packages not in definitions
@@ -193,14 +264,171 @@ func (s *Solver) ConflictsWithInstalled(p pkg.Package) (bool, error) {
return s.ConflictsWith(p, s.Installed())
}
func (s *Solver) Upgrade() ([]pkg.Package, PackagesAssertions, error) {
// UninstallUniverse takes a list of candidate package and return a list of packages that would be removed
// in order to purge the candidate. Uses the solver to check constraints and nothing else
//
// It can be compared to the counterpart Uninstall as this method acts like a uninstall --full
// it removes all the packages and its deps. taking also in consideration other packages that might have
// revdeps
func (s *Solver) UninstallUniverse(toremove pkg.Packages) (pkg.Packages, error) {
if s.noRulesInstalled() {
return s.getList(s.InstalledDatabase, toremove)
}
// resolve to packages from the db
toRemove, err := s.getList(s.InstalledDatabase, toremove)
if err != nil {
return nil, errors.Wrap(err, "Package not found in definition db")
}
var formulas []bf.Formula
r, err := s.BuildInstalled()
if err != nil {
return nil, errors.Wrap(err, "Package not found in definition db")
}
// SAT encode the clauses against the world
for _, p := range toRemove.Unique() {
encodedP, err := p.Encode(s.InstalledDatabase)
if err != nil {
return nil, errors.Wrap(err, "Package not found in definition db")
}
P := bf.Var(encodedP)
formulas = append(formulas, bf.And(bf.Not(P), r))
}
markedForRemoval := pkg.Packages{}
model := bf.Solve(bf.And(formulas...))
if model == nil {
return nil, errors.New("Failed finding a solution")
}
assertion, err := DecodeModel(model, s.InstalledDatabase)
if err != nil {
return nil, errors.Wrap(err, "while decoding model from solution")
}
for _, a := range assertion {
if !a.Value {
if p, err := s.InstalledDatabase.FindPackage(a.Package); err == nil {
markedForRemoval = append(markedForRemoval, p)
}
}
}
return markedForRemoval, nil
}
// UpgradeUniverse mark packages for removal and returns a solution. It considers
// the Universe db as authoritative
// See also on the subject: https://arxiv.org/pdf/1007.1021.pdf
func (s *Solver) UpgradeUniverse(dropremoved bool) (pkg.Packages, PackagesAssertions, error) {
// we first figure out which aren't up-to-date
// which has to be removed
// and which needs to be upgraded
notUptodate := pkg.Packages{}
removed := pkg.Packages{}
toUpgrade := pkg.Packages{}
// TODO: this is memory expensive, we need to optimize this
universe := pkg.NewInMemoryDatabase(false)
for _, p := range s.DefinitionDatabase.World() {
universe.CreatePackage(p)
}
for _, p := range s.Installed() {
universe.CreatePackage(p)
}
// Grab all the installed ones, see if they are eligible for update
for _, p := range s.Installed() {
available, err := universe.FindPackageVersions(p)
if err != nil {
removed = append(removed, p)
}
if len(available) == 0 {
continue
}
bestmatch := available.Best(nil)
// Found a better version available
if !bestmatch.Matches(p) {
notUptodate = append(notUptodate, p)
toUpgrade = append(toUpgrade, bestmatch)
}
}
// resolve to packages from the db to be able to encode correctly
oldPackages, err := s.getList(universe, notUptodate)
if err != nil {
return nil, nil, errors.Wrap(err, "couldn't get package marked for removal from universe")
}
updates, err := s.getList(universe, toUpgrade)
if err != nil {
return nil, nil, errors.Wrap(err, "couldn't get package marked for update from universe")
}
var formulas []bf.Formula
// Build constraints for the whole defdb
r, err := s.BuildWorld(true)
if err != nil {
return nil, nil, errors.Wrap(err, "couldn't build world constraints")
}
// Treat removed packages from universe as marked for deletion
if dropremoved {
oldPackages = append(oldPackages, removed...)
}
// SAT encode the clauses against the world
for _, p := range oldPackages.Unique() {
encodedP, err := p.Encode(universe)
if err != nil {
return nil, nil, errors.Wrap(err, "couldn't encode package")
}
P := bf.Var(encodedP)
formulas = append(formulas, bf.And(bf.Not(P), r))
}
for _, p := range updates {
encodedP, err := p.Encode(universe)
if err != nil {
return nil, nil, errors.Wrap(err, "couldn't encode package")
}
P := bf.Var(encodedP)
formulas = append(formulas, bf.And(P, r))
}
markedForRemoval := pkg.Packages{}
model := bf.Solve(bf.And(formulas...))
if model == nil {
return nil, nil, errors.New("Failed finding a solution")
}
assertion, err := DecodeModel(model, universe)
if err != nil {
return nil, nil, errors.Wrap(err, "while decoding model from solution")
}
for _, a := range assertion {
if !a.Value {
if p, err := s.InstalledDatabase.FindPackage(a.Package); err == nil {
markedForRemoval = append(markedForRemoval, p)
}
}
}
return markedForRemoval, assertion, nil
}
func (s *Solver) Upgrade(checkconflicts, full bool) (pkg.Packages, PackagesAssertions, error) {
// First get candidates that needs to be upgraded..
toUninstall := []pkg.Package{}
toInstall := []pkg.Package{}
toUninstall := pkg.Packages{}
toInstall := pkg.Packages{}
availableCache := map[string][]pkg.Package{}
availableCache := map[string]pkg.Packages{}
for _, p := range s.DefinitionDatabase.World() {
// Each one, should be expanded
availableCache[p.GetName()+p.GetCategory()] = append(availableCache[p.GetName()+p.GetCategory()], p)
@@ -212,7 +440,7 @@ func (s *Solver) Upgrade() ([]pkg.Package, PackagesAssertions, error) {
installedcopy.CreatePackage(p)
packages, ok := availableCache[p.GetName()+p.GetCategory()]
if ok && len(packages) != 0 {
best := pkg.Best(packages)
best := packages.Best(nil)
if best.GetVersion() != p.GetVersion() {
toUninstall = append(toUninstall, p)
toInstall = append(toInstall, best)
@@ -221,9 +449,17 @@ func (s *Solver) Upgrade() ([]pkg.Package, PackagesAssertions, error) {
}
s2 := NewSolver(installedcopy, s.DefinitionDatabase, pkg.NewInMemoryDatabase(false))
s2.SetResolver(s.Resolver)
if !full {
ass := PackagesAssertions{}
for _, i := range toInstall {
ass = append(ass, PackageAssert{Package: i.(*pkg.DefaultPackage), Value: true})
}
}
// Then try to uninstall the versions in the system, and store that tree
for _, p := range toUninstall {
r, err := s.Uninstall(p)
r, err := s.Uninstall(p, checkconflicts, false)
if err != nil {
return nil, nil, errors.Wrap(err, "Could not compute upgrade - couldn't uninstall selected candidate "+p.GetFingerPrint())
}
@@ -233,8 +469,8 @@ func (s *Solver) Upgrade() ([]pkg.Package, PackagesAssertions, error) {
return nil, nil, errors.Wrap(err, "Could not compute upgrade - couldn't remove copy of package targetted for removal")
}
}
}
r, e := s2.Install(toInstall)
return toUninstall, r, e
// To that tree, ask to install the versions that should be upgraded, and try to solve
@@ -244,8 +480,8 @@ func (s *Solver) Upgrade() ([]pkg.Package, PackagesAssertions, error) {
// Uninstall takes a candidate package and return a list of packages that would be removed
// in order to purge the candidate. Returns error if unsat.
func (s *Solver) Uninstall(c pkg.Package) ([]pkg.Package, error) {
var res []pkg.Package
func (s *Solver) Uninstall(c pkg.Package, checkconflicts, full bool) (pkg.Packages, error) {
var res pkg.Packages
candidate, err := s.InstalledDatabase.FindPackage(c)
if err != nil {
@@ -255,13 +491,23 @@ func (s *Solver) Uninstall(c pkg.Package) ([]pkg.Package, error) {
if err != nil || len(packages) == 0 {
candidate = c
} else {
candidate = pkg.Best(packages)
candidate = packages.Best(nil)
}
//Relax search, otherwise we cannot compute solutions for packages not in definitions
// return nil, errors.Wrap(err, "Package not found between installed")
}
// Build a fake "Installed" - Candidate and its requires tree
var InstalledMinusCandidate []pkg.Package
var InstalledMinusCandidate pkg.Packages
// We are asked to not perform a full uninstall (checking all the possible requires that could
// be removed). Let's only check if we can remove the selected package
if !full && checkconflicts {
if conflicts, err := s.Conflicts(candidate, s.Installed()); conflicts {
return nil, err
} else {
return pkg.Packages{candidate}, nil
}
}
// TODO: Can be optimized
for _, i := range s.Installed() {
@@ -276,17 +522,19 @@ func (s *Solver) Uninstall(c pkg.Package) ([]pkg.Package, error) {
}
}
s2 := NewSolver(pkg.NewInMemoryDatabase(false), s.DefinitionDatabase, pkg.NewInMemoryDatabase(false))
s2.SetResolver(s.Resolver)
// Get the requirements to install the candidate
saved := s.InstalledDatabase
s.InstalledDatabase = pkg.NewInMemoryDatabase(false)
asserts, err := s.Install([]pkg.Package{candidate})
asserts, err := s2.Install(pkg.Packages{candidate})
if err != nil {
return nil, err
}
s.InstalledDatabase = saved
for _, a := range asserts {
if a.Value {
if !checkconflicts {
res = append(res, a.Package)
continue
}
c, err := s.ConflictsWithInstalled(a.Package)
if err != nil {
@@ -295,7 +543,7 @@ func (s *Solver) Uninstall(c pkg.Package) ([]pkg.Package, error) {
// If doesn't conflict with installed we just consider it for removal and look for the next one
if !c {
res = append(res, a.Package.IsFlagged(false))
res = append(res, a.Package)
continue
}
@@ -305,7 +553,7 @@ func (s *Solver) Uninstall(c pkg.Package) ([]pkg.Package, error) {
return nil, err
}
if !c {
res = append(res, a.Package.IsFlagged(false))
res = append(res, a.Package)
}
}
@@ -361,13 +609,20 @@ func (s *Solver) solve(f bf.Formula) (map[string]bool, bf.Formula, error) {
// Solve builds the formula given the current state and returns package assertions
func (s *Solver) Solve() (PackagesAssertions, error) {
var model map[string]bool
var err error
f, err := s.BuildFormula()
if err != nil {
return nil, err
}
model, _, err := s.solve(f)
model, _, err = s.solve(f)
if err != nil && s.Resolver != nil {
return s.Resolver.Solve(f, s)
}
if err != nil {
return nil, err
}
@@ -377,7 +632,7 @@ func (s *Solver) Solve() (PackagesAssertions, error) {
// Install given a list of packages, returns package assertions to indicate the packages that must be installed in the system in order
// to statisfy all the constraints
func (s *Solver) Install(c []pkg.Package) (PackagesAssertions, error) {
func (s *Solver) Install(c pkg.Packages) (PackagesAssertions, error) {
coll, err := s.getList(s.DefinitionDatabase, c)
if err != nil {

Some files were not shown because too many files have changed in this diff Show More