Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cd25c346c1 | |||
| 0397443de3 | |||
| 22cd3e1307 | |||
| 0ed1881374 | |||
| 31fe2fe2a6 | |||
| 0dec68294f | |||
| 69ba548dab | |||
| cfa9ff15bb | |||
| ddf7441152 | |||
| 20b4a3fb84 | |||
| 81f4420a67 |
@@ -84,38 +84,3 @@ build-mo:
|
||||
|
||||
release:
|
||||
@goreleaser release --clean
|
||||
|
||||
CLIENT_VM := abra-client
|
||||
vm-client-status:
|
||||
sudo systemctl status microvm@$(CLIENT_VM).service
|
||||
vm-client-create:
|
||||
sudo microvm -f git+file://$$(pwd) -c $(CLIENT_VM)
|
||||
vm-client-start:
|
||||
sudo systemctl start microvm@$(CLIENT_VM).service
|
||||
vm-client-update:
|
||||
sudo microvm -R -f git+file://$$(pwd) -u $(CLIENT_VM)
|
||||
vm-client-run:
|
||||
sudo microvm -f git+file://$$(pwd) -r $(CLIENT_VM)
|
||||
vm-client-stop:
|
||||
sudo systemctl stop microvm@$(CLIENT_VM)
|
||||
vm-client-delete: vm-client-stop
|
||||
sudo rm -rf /var/lib/microvms/$(CLIENT_VM)
|
||||
|
||||
SERVER_VM := abra-server
|
||||
vm-server-status:
|
||||
sudo systemctl status microvm@$(SERVER_VM).service
|
||||
vm-server-create:
|
||||
sudo microvm -f git+file://$$(pwd) -c $(SERVER_VM)
|
||||
vm-server-start:
|
||||
sudo systemctl start microvm@$(SERVER_VM).service
|
||||
vm-server-update:
|
||||
sudo microvm -R -f git+file://$$(pwd) -u $(SERVER_VM)
|
||||
vm-server-run:
|
||||
sudo microvm -f git+file://$$(pwd) -r $(SERVER_VM)
|
||||
vm-server-stop:
|
||||
sudo systemctl stop microvm@$(SERVER_VM)
|
||||
vm-server-delete: vm-stop
|
||||
sudo rm -rf /var/lib/microvms/$(SERVER_VM)
|
||||
|
||||
test-integration:
|
||||
ssh-keygen -t ed25519 -f ./test-integration -q -N "" -C "abra integration test"
|
||||
|
||||
+7
-1
@@ -222,7 +222,13 @@ synchronize your local app environment values with what is deployed live.`),
|
||||
mergedEnv[k] = v
|
||||
}
|
||||
|
||||
if !strings.Contains(recipeEnvVar, ":") {
|
||||
// The recipe version is always carried by TYPE (RECIPE, when present,
|
||||
// is a bare source pointer). Stamp the deployed version onto TYPE and
|
||||
// leave RECIPE untouched so the two never drift apart.
|
||||
if t, ok := mergedEnv["TYPE"]; ok {
|
||||
name, _, _ := strings.Cut(t, ":")
|
||||
mergedEnv["TYPE"] = fmt.Sprintf("%s:%s", name, version)
|
||||
} else {
|
||||
mergedEnv[recipeKey] = fmt.Sprintf("%s:%s", mergedEnv[recipeKey], version)
|
||||
}
|
||||
|
||||
|
||||
+17
-1
@@ -32,6 +32,18 @@ deploy <domain>" to do so.
|
||||
You can see what recipes are available (i.e. values for the [recipe] argument)
|
||||
by running "abra recipe ls".
|
||||
|
||||
In addition to short catalogue names, [recipe] also accepts arbitrary git
|
||||
URLs to use a recipe from outside the catalogue (e.g. a fork or work in
|
||||
progress). Any of these forms is accepted:
|
||||
|
||||
abra app new git.example.com/user/recipe
|
||||
abra app new https://git.example.com/user/recipe
|
||||
abra app new git@git.example.com:user/recipe
|
||||
|
||||
In that case a RECIPE=<canonical name> line is written to the app's .env
|
||||
file so a subsequent "abra app deploy" (on this or another machine) will
|
||||
re-fetch the recipe from the same git source.
|
||||
|
||||
Recipe commit hashes are supported values for "[version]".
|
||||
|
||||
Passing the "--secrets/-S" flag will automatically generate secrets for your
|
||||
@@ -295,7 +307,7 @@ func ensureDomainFlag(recipe recipePkg.Recipe, server string) error {
|
||||
if appDomain == "" && !internal.NoInput {
|
||||
prompt := &survey.Input{
|
||||
Message: i18n.G("Specify app domain"),
|
||||
Default: fmt.Sprintf("%s.%s", recipe.Name, server),
|
||||
Default: fmt.Sprintf("%s.%s", recipe.ShortName(), server),
|
||||
}
|
||||
if err := survey.AskOne(prompt, &appDomain); err != nil {
|
||||
return err
|
||||
@@ -306,6 +318,10 @@ func ensureDomainFlag(recipe recipePkg.Recipe, server string) error {
|
||||
return errors.New(i18n.G("no domain provided"))
|
||||
}
|
||||
|
||||
if strings.ContainsAny(appDomain, "/\\") {
|
||||
return errors.New(i18n.G("invalid domain '%s': must not contain '/' or '\\'", appDomain))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -188,6 +188,12 @@ beforehand. See "abra app backup" for more.`),
|
||||
appPkg.SetChaosVersionLabel(compose, stackName, chosenDowngrade)
|
||||
}
|
||||
|
||||
// NOTE: stamp the deployed version label ourselves rather than relying
|
||||
// on the value baked into the recipe's compose.yml. Git-URL/WIP recipes
|
||||
// often carry a stale or missing label, which would otherwise leave the
|
||||
// live deployment reporting the wrong version after a rollback.
|
||||
appPkg.SetVersionLabel(compose, stackName, chosenDowngrade)
|
||||
|
||||
// Gather secrets
|
||||
secretInfo, err := deploy.GatherSecretsForDeploy(cl, app, internal.ShowUnchanged)
|
||||
if err != nil {
|
||||
|
||||
+24
-4
@@ -200,6 +200,12 @@ beforehand. See "abra app backup" for more.`),
|
||||
appPkg.SetChaosVersionLabel(compose, stackName, chosenUpgrade)
|
||||
}
|
||||
|
||||
// NOTE: stamp the deployed version label ourselves rather than relying
|
||||
// on the value baked into the recipe's compose.yml. Git-URL/WIP recipes
|
||||
// often carry a stale or missing label, which would otherwise leave the
|
||||
// live deployment reporting the wrong version after an upgrade.
|
||||
appPkg.SetVersionLabel(compose, stackName, chosenUpgrade)
|
||||
|
||||
envVars, err := appPkg.CheckEnv(app)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -346,9 +352,17 @@ func getReleaseNotes(
|
||||
return errors.New(i18n.G("parsing chosen upgrade version failed: %s", err))
|
||||
}
|
||||
|
||||
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
||||
if err != nil {
|
||||
return errors.New(i18n.G("parsing deployment version failed: %s", err))
|
||||
// The deployed version can be "unknown" (e.g. deployed without a version
|
||||
// label, as is common for git-URL/work-in-progress recipes). In that case
|
||||
// there is no lower bound to filter by, so we gather notes for every
|
||||
// version below the chosen one instead of failing.
|
||||
var parsedDeployedVersion tagcmp.Tag
|
||||
haveDeployedVersion := deployMeta.Version != config.UNKNOWN_DEFAULT
|
||||
if haveDeployedVersion {
|
||||
parsedDeployedVersion, err = tagcmp.Parse(deployMeta.Version)
|
||||
if err != nil {
|
||||
return errors.New(i18n.G("parsing deployment version failed: %s", err))
|
||||
}
|
||||
}
|
||||
|
||||
for _, version := range internal.SortVersionsDesc(versions) {
|
||||
@@ -357,7 +371,7 @@ func getReleaseNotes(
|
||||
return errors.New(i18n.G("parsing recipe version failed: %s", err))
|
||||
}
|
||||
|
||||
if parsedVersion.IsGreaterThan(parsedDeployedVersion) &&
|
||||
if (!haveDeployedVersion || parsedVersion.IsGreaterThan(parsedDeployedVersion)) &&
|
||||
parsedVersion.IsLessThan(parsedChosenUpgrade) {
|
||||
note, err := app.Recipe.GetReleaseNotes(version, app.Domain)
|
||||
if err != nil {
|
||||
@@ -420,6 +434,12 @@ func validateUpgradeVersionArg(
|
||||
return errors.New(i18n.G("'%s' is not a known version for %s", specificVersion, app.Recipe.Name))
|
||||
}
|
||||
|
||||
// Without a known deployed version we cannot tell whether the requested
|
||||
// version is an upgrade; trust the explicit choice rather than failing.
|
||||
if deployMeta.Version == config.UNKNOWN_DEFAULT {
|
||||
return nil
|
||||
}
|
||||
|
||||
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
||||
if err != nil {
|
||||
return errors.New(i18n.G("'%s' is not a known version", deployMeta.Version))
|
||||
|
||||
@@ -59,9 +59,15 @@ func ValidateRecipe(args []string, cmdName string) recipe.Recipe {
|
||||
log.Fatal(i18n.G("no recipe name provided"))
|
||||
}
|
||||
|
||||
if _, ok := knownRecipes[recipeName]; !ok {
|
||||
if !strings.Contains(recipeName, "/") {
|
||||
log.Fatal(i18n.G("no recipe '%s' exists?", recipeName))
|
||||
recipeName = recipe.NormalizeRecipeName(recipeName)
|
||||
|
||||
lookupName := recipeName
|
||||
if i := strings.LastIndex(lookupName, ":"); i >= 0 {
|
||||
lookupName = lookupName[:i]
|
||||
}
|
||||
if _, ok := knownRecipes[lookupName]; !ok {
|
||||
if !strings.Contains(lookupName, "/") {
|
||||
log.Fatal(i18n.G("no recipe '%s' exists? pass a git URL (e.g. https://git.example.com/user/recipe) to use a recipe outside the catalogue", lookupName))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,13 @@ package recipe
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"coopcloud.tech/abra/cli/internal"
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/abra/pkg/formatter"
|
||||
"coopcloud.tech/abra/pkg/i18n"
|
||||
"coopcloud.tech/abra/pkg/log"
|
||||
@@ -41,6 +43,7 @@ var RecipeListCommand = &cobra.Command{
|
||||
|
||||
headers := []string{
|
||||
i18n.G("name"),
|
||||
i18n.G("source"),
|
||||
i18n.G("category"),
|
||||
i18n.G("status"),
|
||||
i18n.G("healthcheck"),
|
||||
@@ -56,6 +59,7 @@ var RecipeListCommand = &cobra.Command{
|
||||
for _, recipe := range recipes {
|
||||
row := []string{
|
||||
recipe.Name,
|
||||
i18n.G("catalogue"),
|
||||
recipe.Category,
|
||||
strconv.Itoa(recipe.Features.Status),
|
||||
recipe.Features.Healthcheck,
|
||||
@@ -76,6 +80,23 @@ var RecipeListCommand = &cobra.Command{
|
||||
}
|
||||
}
|
||||
|
||||
externals := externalRecipes(catl)
|
||||
sort.Strings(externals)
|
||||
for _, name := range externals {
|
||||
row := []string{
|
||||
name,
|
||||
i18n.G("external"),
|
||||
"-", "-", "-", "-", "-", "-", "-",
|
||||
}
|
||||
if pattern != "" {
|
||||
if !strings.Contains(name, pattern) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
table.Row(row...)
|
||||
rows = append(rows, row)
|
||||
}
|
||||
|
||||
if len(rows) > 0 {
|
||||
if internal.MachineReadable {
|
||||
out, err := formatter.ToJSON(headers, rows)
|
||||
@@ -93,6 +114,30 @@ var RecipeListCommand = &cobra.Command{
|
||||
},
|
||||
}
|
||||
|
||||
// externalRecipes returns canonical names of locally-cloned recipes that
|
||||
// were sourced from an arbitrary git URL (i.e. they carry a .abra-source
|
||||
// sidecar) and are not already listed in the catalogue.
|
||||
func externalRecipes(catl recipe.RecipeCatalogue) []string {
|
||||
dirs, err := recipe.GetRecipesLocal()
|
||||
if err != nil {
|
||||
log.Debug(i18n.G("can't read local recipes: %s", err))
|
||||
return nil
|
||||
}
|
||||
|
||||
var names []string
|
||||
for _, dir := range dirs {
|
||||
canonical := recipe.ReadRecipeSource(path.Join(config.RECIPES_DIR, dir))
|
||||
if canonical == "" {
|
||||
continue
|
||||
}
|
||||
if _, inCatalogue := catl[canonical]; inCatalogue {
|
||||
continue
|
||||
}
|
||||
names = append(names, canonical)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
var (
|
||||
pattern string
|
||||
)
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ func main() {
|
||||
Version = "dev"
|
||||
}
|
||||
if Commit == "" {
|
||||
Commit = " "
|
||||
Commit = "unknown-commit"
|
||||
}
|
||||
|
||||
cli.Run(Version, Commit)
|
||||
|
||||
Generated
-60
@@ -18,48 +18,6 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"home-manager": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1785119570,
|
||||
"narHash": "sha256-Rgs2xKnGLFWQscxUaXX07oyZeuMDOHEbqDOsgliLFGM=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "d4fd24667c8cbef124bb70a20380cab75ec8474d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"ref": "release-26.05",
|
||||
"repo": "home-manager",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"microvm": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"spectrum": "spectrum"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1784666190,
|
||||
"narHash": "sha256-xgfS6slV7J3baMooNN1UuBi51RIgg9y0DbCxfSA0668=",
|
||||
"owner": "astro",
|
||||
"repo": "microvm.nix",
|
||||
"rev": "fa5340ac684cdce8a22b6d4a0bcebb0cc999275e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "astro",
|
||||
"repo": "microvm.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1778443072,
|
||||
@@ -79,27 +37,9 @@
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"home-manager": "home-manager",
|
||||
"microvm": "microvm",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"spectrum": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1783694892,
|
||||
"narHash": "sha256-xO8f7Qng+18FK2UlB9vcrkxCaQMCt5WjCH24aW/11eg=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "24c4346e30fdea8d8e80f34aec3554a15a667d24",
|
||||
"revCount": 1410,
|
||||
"type": "git",
|
||||
"url": "https://spectrum-os.org/git/spectrum"
|
||||
},
|
||||
"original": {
|
||||
"type": "git",
|
||||
"url": "https://spectrum-os.org/git/spectrum"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
|
||||
@@ -3,71 +3,35 @@
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
|
||||
home-manager = {
|
||||
url = "github:nix-community/home-manager/release-26.05";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
microvm = {
|
||||
url = "github:astro/microvm.nix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
microvm,
|
||||
home-manager,
|
||||
flake-utils,
|
||||
}:
|
||||
let
|
||||
system = "x86_64-linux";
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
publicKey = builtins.readFile ./test-integration.pub;
|
||||
privateKey = builtins.readFile ./test-integration;
|
||||
in
|
||||
{
|
||||
nixosConfigurations = {
|
||||
abra-client = nixpkgs.lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
modules = [
|
||||
microvm.nixosModules.microvm
|
||||
home-manager.nixosModules.home-manager
|
||||
./nix/hosts/client/configuration.nix
|
||||
];
|
||||
specialArgs = { inherit publicKey privateKey; };
|
||||
flake-utils.lib.eachDefaultSystem (
|
||||
system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in
|
||||
{
|
||||
packages = rec {
|
||||
abra = pkgs.callPackage ./package.nix { };
|
||||
default = abra;
|
||||
};
|
||||
abra-server = nixpkgs.lib.nixosSystem {
|
||||
system = "x86_64-linux";
|
||||
modules = [
|
||||
microvm.nixosModules.microvm
|
||||
./nix/hosts/server/configuration.nix
|
||||
];
|
||||
specialArgs = { inherit publicKey privateKey; };
|
||||
apps = rec {
|
||||
abra = flake-utils.lib.mkApp { drv = self.packages.${system}.abra; };
|
||||
default = abra;
|
||||
};
|
||||
};
|
||||
packages = rec {
|
||||
abra = pkgs.callPackage ./package.nix { };
|
||||
default = abra;
|
||||
};
|
||||
apps = rec {
|
||||
abra = flake-utils.lib.mkApp { drv = self.packages.${system}.abra; };
|
||||
default = abra;
|
||||
};
|
||||
devShells.${system}.default = pkgs.mkShell {
|
||||
# testing env variables
|
||||
BATS_LIB_PATH = "~/.local/share/bats/";
|
||||
TEST_SERVER = "abra.local";
|
||||
ABRA_DIR = "$HOME/.abra_test";
|
||||
|
||||
packages = with pkgs; [
|
||||
go_1_26
|
||||
gnumake
|
||||
gopls
|
||||
];
|
||||
};
|
||||
|
||||
};
|
||||
devShells.default = pkgs.mkShell {
|
||||
packages = with pkgs; [
|
||||
go_1_26
|
||||
gnumake
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ require (
|
||||
github.com/moby/term v0.5.2
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/schollz/progressbar/v3 v3.19.1
|
||||
golang.org/x/term v0.44.0
|
||||
golang.org/x/term v0.45.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gotest.tools/v3 v3.5.2
|
||||
)
|
||||
@@ -155,7 +155,7 @@ require (
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
golang.org/x/sys v0.46.0
|
||||
golang.org/x/sys v0.47.0
|
||||
)
|
||||
|
||||
replace github.com/docker/cli v28.4.0+incompatible => git.coopcloud.tech/toolshed/docker-cli v28.5.3-0.20260202112816-30df2d0b3a00+incompatible
|
||||
|
||||
@@ -1139,13 +1139,13 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
|
||||
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
|
||||
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
|
||||
golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0=
|
||||
golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
{
|
||||
lib,
|
||||
pkgs,
|
||||
publicKey,
|
||||
privateKey,
|
||||
...
|
||||
}:
|
||||
let
|
||||
index = 2;
|
||||
mac = "00:00:00:00:00:01";
|
||||
serverIp = "10.0.0.3";
|
||||
in
|
||||
{
|
||||
microvm = {
|
||||
vcpu = 4;
|
||||
mem = 2049; # see issue related in microvm repo with 2048, so add 1MB
|
||||
interfaces = [
|
||||
{
|
||||
id = "vm${toString index}";
|
||||
type = "tap";
|
||||
inherit mac;
|
||||
}
|
||||
];
|
||||
shares = [
|
||||
{
|
||||
proto = "virtiofs";
|
||||
tag = "repo";
|
||||
# Source path can be absolute or relative
|
||||
# to /var/lib/microvms/$hostName
|
||||
source = "/home/dev/Documents/abra";
|
||||
mountPoint = "/home/dev/abra";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
boot.tmp = {
|
||||
useTmpfs = true;
|
||||
tmpfsSize = "2G";
|
||||
};
|
||||
|
||||
networking = {
|
||||
hostName = "abra-client";
|
||||
useNetworkd = true;
|
||||
};
|
||||
systemd.network.networks."10-eth" = {
|
||||
matchConfig.MACAddress = mac;
|
||||
# Static IP configuration
|
||||
address = [
|
||||
"10.0.0.${toString index}/32"
|
||||
"fec0::${lib.toHexString index}/128"
|
||||
];
|
||||
routes = [
|
||||
{
|
||||
# A route to the host
|
||||
Destination = "10.0.0.0/32";
|
||||
GatewayOnLink = true;
|
||||
}
|
||||
{
|
||||
# Route to server
|
||||
Destination = "${serverIp}/32";
|
||||
Gateway = "10.0.0.0";
|
||||
GatewayOnLink = true;
|
||||
}
|
||||
{
|
||||
# Default route
|
||||
Destination = "0.0.0.0/0";
|
||||
Gateway = "10.0.0.0";
|
||||
GatewayOnLink = true;
|
||||
}
|
||||
{
|
||||
# Default route
|
||||
Destination = "::/0";
|
||||
Gateway = "fec0::";
|
||||
GatewayOnLink = true;
|
||||
}
|
||||
];
|
||||
networkConfig = {
|
||||
# DNS servers no longer come from DHCP nor Router
|
||||
# Advertisements. Perhaps you want to change the defaults:
|
||||
DNS = [
|
||||
# Quad9.net
|
||||
"9.9.9.9"
|
||||
"149.112.112.112"
|
||||
"2620:fe::fe"
|
||||
"2620:fe::9"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
# open the ports to allow ssh access from the host
|
||||
networking.firewall.allowedTCPPorts = [ 22 ];
|
||||
networking.firewall.allowedUDPPorts = [ 22 ];
|
||||
|
||||
networking.extraHosts = ''
|
||||
${serverIp} abra.local
|
||||
${serverIp} app_check_bats.abra.local
|
||||
'';
|
||||
|
||||
environment.variables = {
|
||||
CGO_ENABLED = 0;
|
||||
TEST_SERVER = "abra.local";
|
||||
ABRA_DIR = "$HOME/.abra_test";
|
||||
};
|
||||
|
||||
users.users.dev = {
|
||||
isNormalUser = true;
|
||||
password = "test";
|
||||
home = "/home/dev";
|
||||
description = "Alice Foobar";
|
||||
extraGroups = [
|
||||
"wheel"
|
||||
"networkmanager"
|
||||
];
|
||||
openssh.authorizedKeys.keys = [
|
||||
publicKey
|
||||
];
|
||||
};
|
||||
# ssh agent with askpass
|
||||
programs.ssh = {
|
||||
startAgent = true;
|
||||
};
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
settings = {
|
||||
PasswordAuthentication = false;
|
||||
PermitRootLogin = "no";
|
||||
AllowUsers = [ "dev" ];
|
||||
};
|
||||
};
|
||||
programs.git.enable = true;
|
||||
|
||||
environment.systemPackages = with pkgs; [
|
||||
# build dependencies, copied from flake.nix
|
||||
go_1_26
|
||||
gnumake
|
||||
# testing dependencies
|
||||
(bats.withLibraries (p: [
|
||||
p.bats-assert
|
||||
p.bats-file
|
||||
p.bats-support
|
||||
]))
|
||||
jq
|
||||
];
|
||||
|
||||
home-manager = {
|
||||
useGlobalPkgs = true;
|
||||
useUserPackages = true;
|
||||
users.dev =
|
||||
{ ... }:
|
||||
{
|
||||
imports = [
|
||||
./home.nix
|
||||
];
|
||||
};
|
||||
# pass args to home-manager
|
||||
extraSpecialArgs = {
|
||||
publicKey = publicKey;
|
||||
privateKey = privateKey;
|
||||
serverIp = serverIp;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
{
|
||||
serverIp,
|
||||
...
|
||||
}:
|
||||
{
|
||||
# Home Manager needs a bit of information about you and the
|
||||
# paths it should manage.
|
||||
home.username = "dev";
|
||||
home.homeDirectory = "/home/dev";
|
||||
|
||||
# This value determines the Home Manager release that your
|
||||
# configuration is compatible with. This helps avoid breakage
|
||||
# when a new Home Manager release introduces backwards
|
||||
# incompatible changes.
|
||||
#
|
||||
# You can update Home Manager without changing this value. See
|
||||
# the Home Manager release notes for a list of state version
|
||||
# changes in each release.
|
||||
home.stateVersion = "26.05";
|
||||
|
||||
# Let Home Manager install and manage itself.
|
||||
programs.home-manager.enable = true;
|
||||
programs.ssh = {
|
||||
enable = true;
|
||||
enableDefaultConfig = false;
|
||||
settings = {
|
||||
"abra.local" = {
|
||||
HostName = serverIp;
|
||||
User = "dev";
|
||||
Port = 22;
|
||||
IdentityFile = "/home/dev/abra/test-integration";
|
||||
};
|
||||
"*.abra.local" = {
|
||||
HostName = serverIp;
|
||||
User = "dev";
|
||||
Port = 22;
|
||||
IdentityFile = "/home/dev/abra/test-integration";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
{
|
||||
lib,
|
||||
publicKey,
|
||||
...
|
||||
}:
|
||||
let
|
||||
index = 3;
|
||||
mac = "00:00:00:00:00:02";
|
||||
clientIp = "10.0.0.2";
|
||||
in
|
||||
{
|
||||
microvm = {
|
||||
vcpu = 2;
|
||||
mem = 2049; # see issue related in microvm repo with 2048, so add 1MB
|
||||
interfaces = [
|
||||
{
|
||||
id = "vm${toString index}";
|
||||
type = "tap";
|
||||
inherit mac;
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
boot.tmp = {
|
||||
useTmpfs = true;
|
||||
tmpfsSize = "2G";
|
||||
};
|
||||
|
||||
networking = {
|
||||
hostName = "abra-server";
|
||||
useNetworkd = true;
|
||||
};
|
||||
systemd.network.networks."11-eth" = {
|
||||
matchConfig.MACAddress = mac;
|
||||
# Static IP configuration
|
||||
address = [
|
||||
"10.0.0.${toString index}/32"
|
||||
"fec0::${lib.toHexString index}/128"
|
||||
];
|
||||
routes = [
|
||||
{
|
||||
# A route to the host
|
||||
Destination = "10.0.0.0/32";
|
||||
GatewayOnLink = true;
|
||||
}
|
||||
{
|
||||
# Route to server
|
||||
Destination = "${clientIp}/32";
|
||||
Gateway = "10.0.0.0";
|
||||
GatewayOnLink = true;
|
||||
}
|
||||
{
|
||||
# Default route
|
||||
Destination = "0.0.0.0/0";
|
||||
Gateway = "10.0.0.0";
|
||||
GatewayOnLink = true;
|
||||
}
|
||||
{
|
||||
# Default route
|
||||
Destination = "::/0";
|
||||
Gateway = "fec0::";
|
||||
GatewayOnLink = true;
|
||||
}
|
||||
];
|
||||
networkConfig = {
|
||||
# DNS servers no longer come from DHCP nor Router
|
||||
# Advertisements. Perhaps you want to change the defaults:
|
||||
DNS = [
|
||||
# Quad9.net
|
||||
"9.9.9.9"
|
||||
"149.112.112.112"
|
||||
"2620:fe::fe"
|
||||
"2620:fe::9"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
# open the ports to allow ssh access from the host
|
||||
networking.firewall.allowedTCPPorts = [
|
||||
22
|
||||
80
|
||||
443
|
||||
];
|
||||
networking.firewall.allowedUDPPorts = [
|
||||
22
|
||||
80
|
||||
443
|
||||
];
|
||||
|
||||
users.users.dev = {
|
||||
isNormalUser = true;
|
||||
password = "test";
|
||||
home = "/home/dev";
|
||||
description = "Alice Foobar";
|
||||
extraGroups = [
|
||||
"wheel"
|
||||
"networkmanager"
|
||||
"docker"
|
||||
];
|
||||
openssh.authorizedKeys.keys = [
|
||||
publicKey
|
||||
];
|
||||
};
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
settings = {
|
||||
PasswordAuthentication = false;
|
||||
PermitRootLogin = "no";
|
||||
AllowUsers = [ "dev" ];
|
||||
};
|
||||
};
|
||||
virtualisation.docker = {
|
||||
enable = true;
|
||||
liveRestore = false;
|
||||
};
|
||||
}
|
||||
+111
-51
@@ -260,12 +260,30 @@ func ReadAppEnvFile(appFile AppFile, name AppName) (App, error) {
|
||||
func NewApp(env envfile.AppEnv, name string, appFile AppFile) (App, error) {
|
||||
domain := env["DOMAIN"]
|
||||
|
||||
recipeName, exists := env["RECIPE"]
|
||||
if !exists {
|
||||
recipeName, exists = env["TYPE"]
|
||||
if !exists {
|
||||
return App{}, errors.New(i18n.G("%s is missing the TYPE env var?", name))
|
||||
}
|
||||
typeVar, hasType := env["TYPE"]
|
||||
recipeVar, hasRecipe := env["RECIPE"]
|
||||
if !hasType && !hasRecipe {
|
||||
return App{}, errors.New(i18n.G("%s is missing the TYPE env var?", name))
|
||||
}
|
||||
|
||||
// The recipe identity is taken from RECIPE when present (it records the
|
||||
// canonical git source for externally-sourced recipes), otherwise from
|
||||
// TYPE. The deployed version is always carried by TYPE when a TYPE line
|
||||
// exists; only legacy apps that set RECIPE alone carry it there. RECIPE
|
||||
// itself must stay a bare source pointer, never a versioned value.
|
||||
identity := typeVar
|
||||
if hasRecipe {
|
||||
identity = recipeVar
|
||||
}
|
||||
|
||||
versionSource := identity
|
||||
if hasType {
|
||||
versionSource = typeVar
|
||||
}
|
||||
|
||||
recipeName, _, _ := strings.Cut(identity, ":")
|
||||
if _, version, found := strings.Cut(versionSource, ":"); found && version != "" {
|
||||
recipeName = fmt.Sprintf("%s:%s", recipeName, version)
|
||||
}
|
||||
|
||||
return App{
|
||||
@@ -392,11 +410,15 @@ func TemplateAppEnvSample(r recipe.Recipe, appName, server, domain string) error
|
||||
|
||||
newContents := strings.Replace(
|
||||
string(read),
|
||||
fmt.Sprintf("%s.example.com", r.Name),
|
||||
fmt.Sprintf("%s.example.com", r.ShortName()),
|
||||
domain,
|
||||
-1,
|
||||
)
|
||||
|
||||
if strings.Contains(r.Name, "/") {
|
||||
newContents = injectRecipeLine(newContents, r.Name)
|
||||
}
|
||||
|
||||
err = os.WriteFile(appEnvPath, []byte(newContents), 0)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -407,6 +429,37 @@ func TemplateAppEnvSample(r recipe.Recipe, appName, server, domain string) error
|
||||
return nil
|
||||
}
|
||||
|
||||
// injectRecipeLine ensures the env file carries a RECIPE=<canonical name>
|
||||
// line so a downstream `abra app deploy` (potentially on another machine)
|
||||
// can re-fetch the recipe from its original git source. If a RECIPE= line
|
||||
// already exists in the copied .env.sample it is replaced; otherwise a new
|
||||
// line is inserted immediately after the TYPE= line, or appended at the
|
||||
// end if no TYPE= line is present.
|
||||
func injectRecipeLine(contents, canonicalName string) string {
|
||||
lines := strings.Split(contents, "\n")
|
||||
for i, line := range lines {
|
||||
trimmed := strings.TrimLeft(line, " \t")
|
||||
if strings.HasPrefix(trimmed, "RECIPE=") {
|
||||
lines[i] = "RECIPE=" + canonicalName
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
}
|
||||
for i, line := range lines {
|
||||
trimmed := strings.TrimLeft(line, " \t")
|
||||
if strings.HasPrefix(trimmed, "TYPE=") {
|
||||
out := make([]string, 0, len(lines)+1)
|
||||
out = append(out, lines[:i+1]...)
|
||||
out = append(out, "RECIPE="+canonicalName)
|
||||
out = append(out, lines[i+1:]...)
|
||||
return strings.Join(out, "\n")
|
||||
}
|
||||
}
|
||||
if contents != "" && !strings.HasSuffix(contents, "\n") {
|
||||
contents += "\n"
|
||||
}
|
||||
return contents + "RECIPE=" + canonicalName + "\n"
|
||||
}
|
||||
|
||||
// SanitiseAppName makes a app name usable with Docker by replacing illegal
|
||||
// characters.
|
||||
func SanitiseAppName(name string) string {
|
||||
@@ -514,10 +567,22 @@ func ExposeAllEnv(
|
||||
_, exists := service.Environment[k]
|
||||
if !exists {
|
||||
value := v
|
||||
if k == "TYPE" || k == "RECIPE" {
|
||||
switch k {
|
||||
case "TYPE":
|
||||
// NOTE(d1): don't use the wrong version from the app env
|
||||
// since we are deploying a new version
|
||||
value = toDeployVersion
|
||||
// since we are deploying a new version. Keep the
|
||||
// recipe name already recorded in TYPE and only
|
||||
// swap in the version being deployed.
|
||||
name, _, _ := strings.Cut(v, ":")
|
||||
if _, version, found := strings.Cut(toDeployVersion, ":"); found {
|
||||
value = fmt.Sprintf("%s:%s", name, version)
|
||||
} else {
|
||||
value = name
|
||||
}
|
||||
case "RECIPE":
|
||||
// RECIPE records the canonical git source only; the
|
||||
// version is carried by TYPE, so keep it bare here.
|
||||
value, _, _ = strings.Cut(toDeployVersion, ":")
|
||||
}
|
||||
service.Environment[k] = &value
|
||||
log.Debug(i18n.G("%s: %s: %s", stackName, k, value))
|
||||
@@ -642,60 +707,55 @@ func (a App) WriteRecipeVersion(version string, dryRun bool) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var (
|
||||
dirtyVersion string
|
||||
skipped bool
|
||||
lines []string
|
||||
scanner = bufio.NewScanner(file)
|
||||
lines []string
|
||||
hasType bool
|
||||
scanner = bufio.NewScanner(file)
|
||||
)
|
||||
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
if !strings.HasPrefix(line, "RECIPE=") && !strings.HasPrefix(line, "TYPE=") {
|
||||
lines = append(lines, line)
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.HasPrefix(line, "#") {
|
||||
lines = append(lines, line)
|
||||
continue
|
||||
}
|
||||
|
||||
if strings.Contains(line, version) && !a.Recipe.Dirty && !strings.HasSuffix(line, config.DIRTY_DEFAULT) {
|
||||
skipped = true
|
||||
lines = append(lines, line)
|
||||
continue
|
||||
}
|
||||
|
||||
splitted := strings.Split(line, ":")
|
||||
|
||||
line = fmt.Sprintf("%s:%s", splitted[0], version)
|
||||
lines = append(lines, line)
|
||||
if strings.HasPrefix(line, "TYPE=") {
|
||||
hasType = true
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
file.Close()
|
||||
log.Fatal(err)
|
||||
}
|
||||
file.Close()
|
||||
|
||||
// The recipe version is carried by a single field: TYPE when present (the
|
||||
// standard recipe key), otherwise the legacy RECIPE key. When both exist
|
||||
// (git-URL recipes) RECIPE stays a bare source pointer and only TYPE is
|
||||
// versioned, so the two can never drift apart.
|
||||
targetPrefix := "RECIPE="
|
||||
if hasType {
|
||||
targetPrefix = "TYPE="
|
||||
}
|
||||
|
||||
for i, line := range lines {
|
||||
if !strings.HasPrefix(line, targetPrefix) || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
name, _, _ := strings.Cut(line, ":")
|
||||
lines[i] = fmt.Sprintf("%s:%s", name, version)
|
||||
}
|
||||
|
||||
if dryRun {
|
||||
log.Debug(i18n.G("skipping writing version %s because dry run", version))
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := os.WriteFile(a.Path, []byte(strings.Join(lines, "\n")), os.ModePerm); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if a.Recipe.Dirty && dirtyVersion != "" {
|
||||
version = dirtyVersion
|
||||
}
|
||||
|
||||
if !dryRun {
|
||||
if err := os.WriteFile(a.Path, []byte(strings.Join(lines, "\n")), os.ModePerm); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
} else {
|
||||
log.Debug(i18n.G("skipping writing version %s because dry run", version))
|
||||
}
|
||||
|
||||
if !skipped {
|
||||
log.Debug(i18n.G("version %s saved to %s.env", version, a.Domain))
|
||||
} else {
|
||||
log.Debug(i18n.G("skipping version %s write as already exists in %s.env", version, a.Domain))
|
||||
}
|
||||
log.Debug(i18n.G("version %s saved to %s.env", version, a.Domain))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package app_test
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
@@ -271,6 +273,60 @@ func TestWriteRecipeVersionOverwrite(t *testing.T) {
|
||||
assert.Equal(t, "foo", app.Recipe.EnvVersion)
|
||||
}
|
||||
|
||||
// TestWriteRecipeVersionSingleField ensures the version is written to exactly
|
||||
// one field: TYPE when present (even alongside a RECIPE source pointer, as with
|
||||
// git-URL recipes), otherwise the legacy RECIPE field. RECIPE must stay bare
|
||||
// when a TYPE line exists so the two can never drift apart.
|
||||
func TestWriteRecipeVersionSingleField(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
input string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "git recipe: TYPE and RECIPE both present",
|
||||
input: "TYPE=myrecipe\nRECIPE=git.example.com/user/myrecipe\nDOMAIN=x\n",
|
||||
want: "TYPE=myrecipe:1.2.3+4.5.6\nRECIPE=git.example.com/user/myrecipe\nDOMAIN=x",
|
||||
},
|
||||
{
|
||||
name: "git recipe: re-versioning replaces only TYPE",
|
||||
input: "TYPE=myrecipe:0.1.0+1.0.0\nRECIPE=git.example.com/user/myrecipe\nDOMAIN=x\n",
|
||||
want: "TYPE=myrecipe:1.2.3+4.5.6\nRECIPE=git.example.com/user/myrecipe\nDOMAIN=x",
|
||||
},
|
||||
{
|
||||
name: "legacy recipe: only RECIPE present",
|
||||
input: "RECIPE=test_recipe\nDOMAIN=x\n",
|
||||
want: "RECIPE=test_recipe:1.2.3+4.5.6\nDOMAIN=x",
|
||||
},
|
||||
{
|
||||
name: "catalogue recipe: only TYPE present",
|
||||
input: "TYPE=nextcloud\nDOMAIN=x\n",
|
||||
want: "TYPE=nextcloud:1.2.3+4.5.6\nDOMAIN=x",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
p := filepath.Join(t.TempDir(), "app.env")
|
||||
if err := os.WriteFile(p, []byte(tc.input), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
app := appPkg.App{Path: p, Domain: "x"}
|
||||
if err := app.WriteRecipeVersion("1.2.3+4.5.6", false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
got, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.want, string(got))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestWriteRecipeVersionUnknown(t *testing.T) {
|
||||
test.Setup()
|
||||
t.Cleanup(func() { test.Teardown() })
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
package app
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestInjectRecipeLineAfterType(t *testing.T) {
|
||||
in := "DOMAIN=example.com\nTYPE=foo\nVERSION=1.0\n"
|
||||
want := "DOMAIN=example.com\nTYPE=foo\nRECIPE=org/foo\nVERSION=1.0\n"
|
||||
got := injectRecipeLine(in, "org/foo")
|
||||
if got != want {
|
||||
t.Errorf("injectRecipeLine inserted RECIPE in wrong position\nwant:\n%q\ngot:\n%q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInjectRecipeLineReplacesExisting(t *testing.T) {
|
||||
in := "TYPE=foo\nRECIPE=old\nDOMAIN=example.com\n"
|
||||
want := "TYPE=foo\nRECIPE=org/foo\nDOMAIN=example.com\n"
|
||||
got := injectRecipeLine(in, "org/foo")
|
||||
if got != want {
|
||||
t.Errorf("injectRecipeLine should replace existing RECIPE line\nwant:\n%q\ngot:\n%q", want, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInjectRecipeLineNoTypeAppends(t *testing.T) {
|
||||
in := "DOMAIN=example.com\n"
|
||||
want := "DOMAIN=example.com\nRECIPE=org/foo\n"
|
||||
got := injectRecipeLine(in, "org/foo")
|
||||
if got != want {
|
||||
t.Errorf("injectRecipeLine should append when TYPE is missing\nwant:\n%q\ngot:\n%q", want, got)
|
||||
}
|
||||
}
|
||||
@@ -27,10 +27,16 @@ var BoldUnderlineStyle = lipgloss.NewStyle().
|
||||
Underline(true)
|
||||
|
||||
func ShortenID(str string) string {
|
||||
if len(str) < 12 {
|
||||
return str
|
||||
}
|
||||
return str[:12]
|
||||
}
|
||||
|
||||
func SmallSHA(hash string) string {
|
||||
if len(hash) < 8 {
|
||||
return hash
|
||||
}
|
||||
return hash[:8]
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,11 @@ func IsClean(repoPath string) (bool, error) {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// Ignore the abra-managed sidecar file that records the canonical
|
||||
// source URL for externally-cloned recipes; it lives alongside the
|
||||
// recipe but is not part of the upstream tree.
|
||||
patterns = append(patterns, gitignore.ParsePattern(".abra-source", nil))
|
||||
|
||||
if len(patterns) > 0 {
|
||||
worktree.Excludes = append(patterns, worktree.Excludes...)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ package git
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
@@ -13,3 +15,37 @@ func TestIsClean(t *testing.T) {
|
||||
assert.Equal(t, isClean, false)
|
||||
assert.True(t, errors.Is(err, git.ErrRepositoryNotExists))
|
||||
}
|
||||
|
||||
// TestIsCleanIgnoresAbraSource confirms that the .abra-source sidecar
|
||||
// file written by abra next to externally-cloned recipes does not cause
|
||||
// IsClean to report the worktree as dirty.
|
||||
func TestIsCleanIgnoresAbraSource(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
||||
if _, err := git.PlainInit(dir, false); err != nil {
|
||||
t.Fatalf("git init failed: %s", err)
|
||||
}
|
||||
|
||||
sidecar := filepath.Join(dir, ".abra-source")
|
||||
if err := os.WriteFile(sidecar, []byte("git.example.com/u/recipe\n"), 0o644); err != nil {
|
||||
t.Fatalf("writing sidecar failed: %s", err)
|
||||
}
|
||||
|
||||
isClean, err := IsClean(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("IsClean returned error: %s", err)
|
||||
}
|
||||
assert.True(t, isClean, "expected worktree with only .abra-source to be reported clean")
|
||||
|
||||
// Sanity check: an unrelated untracked file should still mark it dirty.
|
||||
other := filepath.Join(dir, "random.txt")
|
||||
if err := os.WriteFile(other, []byte("hello"), 0o644); err != nil {
|
||||
t.Fatalf("writing extra file failed: %s", err)
|
||||
}
|
||||
|
||||
isClean, err = IsClean(dir)
|
||||
if err != nil {
|
||||
t.Fatalf("IsClean returned error: %s", err)
|
||||
}
|
||||
assert.False(t, isClean, "expected worktree with unrelated untracked file to be reported dirty")
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -19,6 +20,11 @@ import (
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
)
|
||||
|
||||
// SourceFile is the sidecar file written next to an externally-cloned
|
||||
// recipe so the canonical "host/path" name can be recovered later (the
|
||||
// on-disk directory name escapes "/" and "." to "_", which is lossy).
|
||||
const SourceFile = ".abra-source"
|
||||
|
||||
type EnsureContext struct {
|
||||
Chaos bool
|
||||
Offline bool
|
||||
@@ -78,6 +84,12 @@ func (r Recipe) EnsureExists() error {
|
||||
if err := gitPkg.Clone(r.Dir, r.GitURL); err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.Contains(r.Name, "/") {
|
||||
sidecar := path.Join(r.Dir, SourceFile)
|
||||
if err := os.WriteFile(sidecar, []byte(r.Name+"\n"), 0o644); err != nil {
|
||||
log.Debug(i18n.G("failed to write recipe source sidecar %s: %s", sidecar, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := gitPkg.EnsureGitRepo(r.Dir); err != nil {
|
||||
@@ -87,6 +99,18 @@ func (r Recipe) EnsureExists() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadRecipeSource returns the canonical name recorded in the .abra-source
|
||||
// sidecar inside the given recipe directory, or the empty string if no
|
||||
// sidecar exists. This lets callers recover the unescaped "host/path"
|
||||
// form for externally-cloned recipes.
|
||||
func ReadRecipeSource(recipeDir string) string {
|
||||
data, err := os.ReadFile(path.Join(recipeDir, SourceFile))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(string(data))
|
||||
}
|
||||
|
||||
// IsChaosCommit determines if a version sttring is a chaos commit or not.
|
||||
func (r Recipe) IsChaosCommit(version string) (bool, error) {
|
||||
isChaosCommit := false
|
||||
@@ -163,6 +187,9 @@ func (r Recipe) EnsureVersion(version string) (bool, error) {
|
||||
|
||||
hash, err := repo.ResolveRevision(plumbing.Revision(version))
|
||||
if err != nil {
|
||||
if isRemoteBranch(repo, version) {
|
||||
log.Fatal(i18n.G("'%s' is a branch name; ':<version>' only supports tags or commit hashes, not branches", version))
|
||||
}
|
||||
log.Fatal(i18n.G("unable to resolve '%s': %s", version, err))
|
||||
}
|
||||
|
||||
@@ -479,6 +506,28 @@ func (r Recipe) GetRecipeVersions() (RecipeVersions, []string, error) {
|
||||
return versions, uniqueWarnings, nil
|
||||
}
|
||||
|
||||
// isRemoteBranch reports whether name matches a branch on any configured
|
||||
// remote. Used to give a clearer error when a user passes a branch as the
|
||||
// ":version" suffix, which is unsupported.
|
||||
func isRemoteBranch(repo *git.Repository, name string) bool {
|
||||
remotes, err := repo.Remotes()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
for _, remote := range remotes {
|
||||
refs, err := remote.List(&git.ListOptions{})
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, ref := range refs {
|
||||
if ref.Name().IsBranch() && ref.Name().Short() == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Head retrieves latest HEAD metadata.
|
||||
func (r Recipe) Head() (*plumbing.Reference, error) {
|
||||
repo, err := git.PlainOpen(r.Dir)
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -121,7 +122,84 @@ type Features struct {
|
||||
SSO string `json:"sso"`
|
||||
}
|
||||
|
||||
// scpURLPattern matches SCP-style git URLs like git@host:path or git@host:port/path.
|
||||
// Captures: 1=host(:port)?, 2=path.
|
||||
var scpURLPattern = regexp.MustCompile(`^[\w.-]+@([\w.-]+(?::\d+)?):(.+)$`)
|
||||
|
||||
// NormalizeRecipeName canonicalizes a recipe identifier to a stable
|
||||
// "host/path" form (or returns short catalog names unchanged). Accepts:
|
||||
//
|
||||
// - https://host/path[.git][:version]
|
||||
// - http://host/path[.git][:version]
|
||||
// - ssh://git@host[:port]/path[.git][:version]
|
||||
// - git@host:path[.git][:version] (SCP-style)
|
||||
// - host/path[.git][:version] (already canonical)
|
||||
// - short-name[:version] (catalog recipe, pass through)
|
||||
//
|
||||
// The optional trailing :version suffix is preserved verbatim. The .git
|
||||
// suffix and trailing slashes on the path are stripped so the four URL
|
||||
// forms of the same repository collapse to one canonical value.
|
||||
func NormalizeRecipeName(input string) string {
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
// Split off the version suffix first, but only if it's not part of a
|
||||
// scheme (https://) or SCP-style git@host: prefix. The simplest way is
|
||||
// to detect those prefixes and treat the rest as the path-with-version.
|
||||
var (
|
||||
body = input
|
||||
version string
|
||||
)
|
||||
|
||||
switch {
|
||||
case strings.HasPrefix(input, "https://") || strings.HasPrefix(input, "http://") || strings.HasPrefix(input, "ssh://"):
|
||||
u, err := url.Parse(input)
|
||||
if err != nil {
|
||||
return input
|
||||
}
|
||||
host := u.Hostname() // strip port to keep canonical form colon-free
|
||||
p := strings.TrimPrefix(u.Path, "/")
|
||||
p, version = splitVersion(p)
|
||||
body = host + "/" + p
|
||||
case scpURLPattern.MatchString(input):
|
||||
m := scpURLPattern.FindStringSubmatch(input)
|
||||
host := m[1]
|
||||
if i := strings.Index(host, ":"); i >= 0 {
|
||||
host = host[:i] // strip port
|
||||
}
|
||||
p, v := splitVersion(m[2])
|
||||
body = host + "/" + p
|
||||
version = v
|
||||
default:
|
||||
body, version = splitVersion(input)
|
||||
}
|
||||
|
||||
body = strings.TrimSuffix(body, "/")
|
||||
body = strings.TrimSuffix(body, ".git")
|
||||
|
||||
if version != "" {
|
||||
return body + ":" + version
|
||||
}
|
||||
return body
|
||||
}
|
||||
|
||||
// splitVersion separates a trailing ":version" suffix from a recipe path.
|
||||
// It only treats the final colon-separated segment as a version when it is
|
||||
// non-empty and contains no slash (a slash means the colon belonged to the
|
||||
// path, e.g. a host:port or scheme separator).
|
||||
func splitVersion(s string) (string, string) {
|
||||
idx := strings.LastIndex(s, ":")
|
||||
if idx < 0 {
|
||||
return s, ""
|
||||
}
|
||||
candidate := s[idx+1:]
|
||||
if candidate == "" || strings.Contains(candidate, "/") {
|
||||
return s, ""
|
||||
}
|
||||
return s[:idx], candidate
|
||||
}
|
||||
|
||||
func Get(name string) Recipe {
|
||||
name = NormalizeRecipeName(name)
|
||||
version := ""
|
||||
versionRaw := ""
|
||||
if strings.Contains(name, ":") {
|
||||
@@ -209,6 +287,16 @@ func (r Recipe) String() string {
|
||||
return out
|
||||
}
|
||||
|
||||
// ShortName returns the final path segment of the recipe name, i.e. the
|
||||
// bare recipe name without any "host/org/" prefix carried by externally
|
||||
// sourced (git URL) recipes. For catalogue recipes it returns Name unchanged.
|
||||
func (r Recipe) ShortName() string {
|
||||
if i := strings.LastIndex(r.Name, "/"); i >= 0 {
|
||||
return r.Name[i+1:]
|
||||
}
|
||||
return r.Name
|
||||
}
|
||||
|
||||
func escapeRecipeName(recipeName string) string {
|
||||
recipeName = strings.ReplaceAll(recipeName, "/", "_")
|
||||
recipeName = strings.ReplaceAll(recipeName, ".", "_")
|
||||
|
||||
@@ -88,6 +88,88 @@ func TestGet(t *testing.T) {
|
||||
AbraShPath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/abra.sh"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "https://mygit.org/myorg/cool-recipe",
|
||||
recipe: Recipe{
|
||||
Name: "mygit.org/myorg/cool-recipe",
|
||||
Dir: path.Join(cfg.GetAbraDir(), "/recipes/mygit_org_myorg_cool-recipe"),
|
||||
GitURL: "https://mygit.org/myorg/cool-recipe.git",
|
||||
SSHURL: "ssh://git@mygit.org/myorg/cool-recipe.git",
|
||||
ComposePath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/compose.yml"),
|
||||
ReadmePath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/README.md"),
|
||||
SampleEnvPath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/.env.sample"),
|
||||
AbraShPath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/abra.sh"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "https://mygit.org/myorg/cool-recipe.git",
|
||||
recipe: Recipe{
|
||||
Name: "mygit.org/myorg/cool-recipe",
|
||||
Dir: path.Join(cfg.GetAbraDir(), "/recipes/mygit_org_myorg_cool-recipe"),
|
||||
GitURL: "https://mygit.org/myorg/cool-recipe.git",
|
||||
SSHURL: "ssh://git@mygit.org/myorg/cool-recipe.git",
|
||||
ComposePath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/compose.yml"),
|
||||
ReadmePath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/README.md"),
|
||||
SampleEnvPath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/.env.sample"),
|
||||
AbraShPath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/abra.sh"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "https://mygit.org/myorg/cool-recipe.git:1.2.4",
|
||||
recipe: Recipe{
|
||||
Name: "mygit.org/myorg/cool-recipe",
|
||||
EnvVersion: "1.2.4",
|
||||
EnvVersionRaw: "1.2.4",
|
||||
Dir: path.Join(cfg.GetAbraDir(), "/recipes/mygit_org_myorg_cool-recipe"),
|
||||
GitURL: "https://mygit.org/myorg/cool-recipe.git",
|
||||
SSHURL: "ssh://git@mygit.org/myorg/cool-recipe.git",
|
||||
ComposePath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/compose.yml"),
|
||||
ReadmePath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/README.md"),
|
||||
SampleEnvPath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/.env.sample"),
|
||||
AbraShPath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/abra.sh"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ssh://git@mygit.org/myorg/cool-recipe.git",
|
||||
recipe: Recipe{
|
||||
Name: "mygit.org/myorg/cool-recipe",
|
||||
Dir: path.Join(cfg.GetAbraDir(), "/recipes/mygit_org_myorg_cool-recipe"),
|
||||
GitURL: "https://mygit.org/myorg/cool-recipe.git",
|
||||
SSHURL: "ssh://git@mygit.org/myorg/cool-recipe.git",
|
||||
ComposePath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/compose.yml"),
|
||||
ReadmePath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/README.md"),
|
||||
SampleEnvPath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/.env.sample"),
|
||||
AbraShPath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/abra.sh"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "git@mygit.org:myorg/cool-recipe.git",
|
||||
recipe: Recipe{
|
||||
Name: "mygit.org/myorg/cool-recipe",
|
||||
Dir: path.Join(cfg.GetAbraDir(), "/recipes/mygit_org_myorg_cool-recipe"),
|
||||
GitURL: "https://mygit.org/myorg/cool-recipe.git",
|
||||
SSHURL: "ssh://git@mygit.org/myorg/cool-recipe.git",
|
||||
ComposePath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/compose.yml"),
|
||||
ReadmePath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/README.md"),
|
||||
SampleEnvPath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/.env.sample"),
|
||||
AbraShPath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/abra.sh"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "git@mygit.org:myorg/cool-recipe:1.2.4",
|
||||
recipe: Recipe{
|
||||
Name: "mygit.org/myorg/cool-recipe",
|
||||
EnvVersion: "1.2.4",
|
||||
EnvVersionRaw: "1.2.4",
|
||||
Dir: path.Join(cfg.GetAbraDir(), "/recipes/mygit_org_myorg_cool-recipe"),
|
||||
GitURL: "https://mygit.org/myorg/cool-recipe.git",
|
||||
SSHURL: "ssh://git@mygit.org/myorg/cool-recipe.git",
|
||||
ComposePath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/compose.yml"),
|
||||
ReadmePath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/README.md"),
|
||||
SampleEnvPath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/.env.sample"),
|
||||
AbraShPath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/abra.sh"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
@@ -101,6 +183,49 @@ func TestGet(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeRecipeName(t *testing.T) {
|
||||
cases := []struct {
|
||||
in, want string
|
||||
}{
|
||||
// catalog short names pass through
|
||||
{"foo", "foo"},
|
||||
{"foo:1.2.3", "foo:1.2.3"},
|
||||
|
||||
// bare host/path form
|
||||
{"mygit.org/myorg/cool-recipe", "mygit.org/myorg/cool-recipe"},
|
||||
{"mygit.org/myorg/cool-recipe.git", "mygit.org/myorg/cool-recipe"},
|
||||
{"mygit.org/myorg/cool-recipe.git:1.2.3", "mygit.org/myorg/cool-recipe:1.2.3"},
|
||||
{"mygit.org/myorg/cool-recipe/", "mygit.org/myorg/cool-recipe"},
|
||||
|
||||
// https://
|
||||
{"https://mygit.org/myorg/cool-recipe", "mygit.org/myorg/cool-recipe"},
|
||||
{"https://mygit.org/myorg/cool-recipe.git", "mygit.org/myorg/cool-recipe"},
|
||||
{"https://mygit.org/myorg/cool-recipe.git:1.2.3", "mygit.org/myorg/cool-recipe:1.2.3"},
|
||||
{"http://mygit.org/myorg/cool-recipe", "mygit.org/myorg/cool-recipe"},
|
||||
|
||||
// ssh://, with and without port
|
||||
{"ssh://git@mygit.org/myorg/cool-recipe.git", "mygit.org/myorg/cool-recipe"},
|
||||
{"ssh://git@mygit.org:2222/myorg/cool-recipe.git", "mygit.org/myorg/cool-recipe"},
|
||||
|
||||
// SCP-style git@host:path
|
||||
{"git@mygit.org:myorg/cool-recipe", "mygit.org/myorg/cool-recipe"},
|
||||
{"git@mygit.org:myorg/cool-recipe.git", "mygit.org/myorg/cool-recipe"},
|
||||
{"git@mygit.org:myorg/cool-recipe:1.2.3", "mygit.org/myorg/cool-recipe:1.2.3"},
|
||||
|
||||
// whitespace
|
||||
{" https://mygit.org/myorg/cool-recipe ", "mygit.org/myorg/cool-recipe"},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.in, func(t *testing.T) {
|
||||
got := NormalizeRecipeName(tc.in)
|
||||
if got != tc.want {
|
||||
t.Errorf("NormalizeRecipeName(%q) = %q, want %q", tc.in, got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetVersionLabelLocalDoesNotUseTimeoutLabel(t *testing.T) {
|
||||
test.Setup()
|
||||
t.Cleanup(func() { test.Teardown() })
|
||||
|
||||
@@ -3,7 +3,7 @@ version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: nginx:1.31.2
|
||||
image: nginx:1.31.3
|
||||
secrets:
|
||||
- test_pass_one
|
||||
- test_pass_two
|
||||
|
||||
@@ -3,7 +3,7 @@ version: "3.8"
|
||||
|
||||
services:
|
||||
app:
|
||||
image: nginx:1.31.2
|
||||
image: nginx:1.31.3
|
||||
networks:
|
||||
- proxy
|
||||
deploy:
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.26@sha256:f96cc555eb8db430159a3aa6797cd5bae561945b7b0fe7d0e284c63a3b291609
|
||||
FROM golang:1.26@sha256:b900de91b15b2e2953d930ece1d0ecff0a1590ab2006088d20dcf0f56f1e979f
|
||||
|
||||
ENV GOOS=linux
|
||||
ENV GOARCH=arm
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.26@sha256:f96cc555eb8db430159a3aa6797cd5bae561945b7b0fe7d0e284c63a3b291609
|
||||
FROM golang:1.26@sha256:b900de91b15b2e2953d930ece1d0ecff0a1590ab2006088d20dcf0f56f1e979f
|
||||
|
||||
ENV GOOS=linux
|
||||
ENV GOARCH=arm64
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
codespell==2.4.2
|
||||
codespell==2.4.3
|
||||
|
||||
+37
-25
@@ -6,38 +6,50 @@ package cpu
|
||||
|
||||
import "strconv"
|
||||
|
||||
// parseRelease parses a dot-separated version number. It follows the semver
|
||||
// syntax, but allows the minor and patch versions to be elided.
|
||||
// parseRelease parses a dot-separated version number from the prefix
|
||||
// of rel. It returns ok=true only if at least the major and minor
|
||||
// components were successfully parsed; the patch component is
|
||||
// best-effort. Trailing vendor or build suffixes such as
|
||||
// "-generic", "+", "_hi3535", or "-rc1" are ignored.
|
||||
//
|
||||
// This is a copy of the Go runtime's parseRelease from
|
||||
// https://golang.org/cl/209597.
|
||||
// https://golang.org/cl/209597, updated in https://golang.org/cl/781800.
|
||||
func parseRelease(rel string) (major, minor, patch int, ok bool) {
|
||||
// Strip anything after a dash or plus.
|
||||
for i := range len(rel) {
|
||||
if rel[i] == '-' || rel[i] == '+' {
|
||||
rel = rel[:i]
|
||||
break
|
||||
// next consumes a run of decimal digits from the front of rel,
|
||||
// returning the parsed value. If the digits are followed by a
|
||||
// '.', it is consumed and more is set so the caller knows to
|
||||
// parse another component; otherwise scanning terminates and
|
||||
// the rest of rel is discarded.
|
||||
next := func() (n int, more, ok bool) {
|
||||
i := 0
|
||||
for i < len(rel) && rel[i] >= '0' && rel[i] <= '9' {
|
||||
i++
|
||||
}
|
||||
if i == 0 {
|
||||
return 0, false, false
|
||||
}
|
||||
n, err := strconv.Atoi(rel[:i])
|
||||
if err != nil {
|
||||
return 0, false, false
|
||||
}
|
||||
if i < len(rel) && rel[i] == '.' {
|
||||
rel = rel[i+1:]
|
||||
return n, true, true
|
||||
}
|
||||
rel = ""
|
||||
return n, false, true
|
||||
}
|
||||
|
||||
next := func() (int, bool) {
|
||||
for i := range len(rel) {
|
||||
if rel[i] == '.' {
|
||||
ver, err := strconv.Atoi(rel[:i])
|
||||
rel = rel[i+1:]
|
||||
return ver, err == nil
|
||||
}
|
||||
}
|
||||
ver, err := strconv.Atoi(rel)
|
||||
rel = ""
|
||||
return ver, err == nil
|
||||
var more bool
|
||||
if major, more, ok = next(); !ok || !more {
|
||||
return 0, 0, 0, false
|
||||
}
|
||||
if major, ok = next(); !ok || rel == "" {
|
||||
return
|
||||
if minor, more, ok = next(); !ok {
|
||||
return 0, 0, 0, false
|
||||
}
|
||||
if minor, ok = next(); !ok || rel == "" {
|
||||
return
|
||||
if !more {
|
||||
return major, minor, 0, true
|
||||
}
|
||||
patch, ok = next()
|
||||
return
|
||||
patch, _, _ = next()
|
||||
return major, minor, patch, true
|
||||
}
|
||||
|
||||
+1
@@ -1874,6 +1874,7 @@ func Dup2(oldfd, newfd int) error {
|
||||
//sys Dup3(oldfd int, newfd int, flags int) (err error)
|
||||
//sysnb EpollCreate1(flag int) (fd int, err error)
|
||||
//sysnb EpollCtl(epfd int, op int, fd int, event *EpollEvent) (err error)
|
||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) = SYS_EPOLL_PWAIT
|
||||
//sys Eventfd(initval uint, flags int) (fd int, err error) = SYS_EVENTFD2
|
||||
//sys Exit(code int) = SYS_EXIT_GROUP
|
||||
//sys Fallocate(fd int, mode uint32, off int64, len int64) (err error)
|
||||
|
||||
-1
@@ -20,7 +20,6 @@ func setTimeval(sec, usec int64) Timeval {
|
||||
|
||||
// 64-bit file system and 32-bit uid calls
|
||||
// (386 default is 32-bit file system and 16-bit uid).
|
||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
|
||||
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64_64
|
||||
//sys Fchown(fd int, uid int, gid int) (err error) = SYS_FCHOWN32
|
||||
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64
|
||||
|
||||
-1
@@ -6,7 +6,6 @@
|
||||
|
||||
package unix
|
||||
|
||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
|
||||
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||
//sys Fstat(fd int, stat *Stat_t) (err error)
|
||||
|
||||
-1
@@ -44,7 +44,6 @@ func Seek(fd int, offset int64, whence int) (newoffset int64, err error) {
|
||||
|
||||
// 64-bit file system and 32-bit uid calls
|
||||
// (16-bit uid calls are not always supported in newer kernels)
|
||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
|
||||
//sys Fchown(fd int, uid int, gid int) (err error) = SYS_FCHOWN32
|
||||
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64
|
||||
//sys Fstatat(dirfd int, path string, stat *Stat_t, flags int) (err error) = SYS_FSTATAT64
|
||||
|
||||
-1
@@ -8,7 +8,6 @@ package unix
|
||||
|
||||
import "unsafe"
|
||||
|
||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) = SYS_EPOLL_PWAIT
|
||||
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||
//sys Fstat(fd int, stat *Stat_t) (err error)
|
||||
|
||||
-1
@@ -8,7 +8,6 @@ package unix
|
||||
|
||||
import "unsafe"
|
||||
|
||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) = SYS_EPOLL_PWAIT
|
||||
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||
//sys Fstatfs(fd int, buf *Statfs_t) (err error)
|
||||
|
||||
-1
@@ -6,7 +6,6 @@
|
||||
|
||||
package unix
|
||||
|
||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
|
||||
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||
//sys Fstatfs(fd int, buf *Statfs_t) (err error)
|
||||
|
||||
-1
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
func Syscall9(trap, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err syscall.Errno)
|
||||
|
||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
|
||||
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||
//sys Ftruncate(fd int, length int64) (err error) = SYS_FTRUNCATE64
|
||||
|
||||
-1
@@ -11,7 +11,6 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
|
||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64
|
||||
//sys Fstatat(dirfd int, path string, stat *Stat_t, flags int) (err error) = SYS_FSTATAT64
|
||||
|
||||
-1
@@ -6,7 +6,6 @@
|
||||
|
||||
package unix
|
||||
|
||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
|
||||
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||
//sys Fstat(fd int, stat *Stat_t) (err error)
|
||||
|
||||
-1
@@ -8,7 +8,6 @@ package unix
|
||||
|
||||
import "unsafe"
|
||||
|
||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) = SYS_EPOLL_PWAIT
|
||||
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||
//sys Fstat(fd int, stat *Stat_t) (err error)
|
||||
|
||||
-1
@@ -10,7 +10,6 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
|
||||
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||
//sys Fstat(fd int, stat *Stat_t) (err error)
|
||||
|
||||
-1
@@ -6,7 +6,6 @@
|
||||
|
||||
package unix
|
||||
|
||||
//sys EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error)
|
||||
//sys Fadvise(fd int, offset int64, length int64, advice int) (err error) = SYS_FADVISE64
|
||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||
//sys Fstat(fd int, stat *Stat_t) (err error)
|
||||
|
||||
+7
-1
@@ -1359,6 +1359,7 @@ const (
|
||||
FAN_UNLIMITED_MARKS = 0x20
|
||||
FAN_UNLIMITED_QUEUE = 0x10
|
||||
FD_CLOEXEC = 0x1
|
||||
FD_PIDFS_ROOT = -0x2712
|
||||
FD_SETSIZE = 0x400
|
||||
FF0 = 0x0
|
||||
FIB_RULE_DEV_DETACHED = 0x8
|
||||
@@ -1970,6 +1971,8 @@ const (
|
||||
MADV_DONTNEED = 0x4
|
||||
MADV_DONTNEED_LOCKED = 0x18
|
||||
MADV_FREE = 0x8
|
||||
MADV_GUARD_INSTALL = 0x66
|
||||
MADV_GUARD_REMOVE = 0x67
|
||||
MADV_HUGEPAGE = 0xe
|
||||
MADV_HWPOISON = 0x64
|
||||
MADV_KEEPONFORK = 0x13
|
||||
@@ -2114,7 +2117,7 @@ const (
|
||||
MS_NOSEC = 0x10000000
|
||||
MS_NOSUID = 0x2
|
||||
MS_NOSYMFOLLOW = 0x100
|
||||
MS_NOUSER = -0x80000000
|
||||
MS_NOUSER = 0x80000000
|
||||
MS_POSIXACL = 0x10000
|
||||
MS_PRIVATE = 0x40000
|
||||
MS_RDONLY = 0x1
|
||||
@@ -3786,6 +3789,9 @@ const (
|
||||
TCPOPT_TIMESTAMP = 0x8
|
||||
TCPOPT_TSTAMP_HDR = 0x101080a
|
||||
TCPOPT_WINDOW = 0x3
|
||||
TCP_AO_KEYF_EXCLUDE_OPT = 0x2
|
||||
TCP_AO_KEYF_IFINDEX = 0x1
|
||||
TCP_AO_MAXKEYLEN = 0x50
|
||||
TCP_CC_INFO = 0x1a
|
||||
TCP_CM_INQ = 0x24
|
||||
TCP_CONGESTION = 0xd
|
||||
|
||||
+17
@@ -700,6 +700,23 @@ func EpollCtl(epfd int, op int, fd int, event *EpollEvent) (err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(events) > 0 {
|
||||
_p0 = unsafe.Pointer(&events[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_EPOLL_PWAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
||||
n = int(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Eventfd(initval uint, flags int) (fd int, err error) {
|
||||
r0, _, e1 := Syscall(SYS_EVENTFD2, uintptr(initval), uintptr(flags), 0)
|
||||
fd = int(r0)
|
||||
|
||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(events) > 0 {
|
||||
_p0 = unsafe.Pointer(&events[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
||||
n = int(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||
_, _, e1 := Syscall6(SYS_FADVISE64_64, uintptr(fd), uintptr(offset), uintptr(offset>>32), uintptr(length), uintptr(length>>32), uintptr(advice))
|
||||
if e1 != 0 {
|
||||
|
||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(events) > 0 {
|
||||
_p0 = unsafe.Pointer(&events[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
||||
n = int(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
||||
if e1 != 0 {
|
||||
|
||||
-17
@@ -213,23 +213,6 @@ func sendmsg(s int, msg *Msghdr, flags int) (n int, err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(events) > 0 {
|
||||
_p0 = unsafe.Pointer(&events[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
||||
n = int(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fchown(fd int, uid int, gid int) (err error) {
|
||||
_, _, e1 := Syscall(SYS_FCHOWN32, uintptr(fd), uintptr(uid), uintptr(gid))
|
||||
if e1 != 0 {
|
||||
|
||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(events) > 0 {
|
||||
_p0 = unsafe.Pointer(&events[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_EPOLL_PWAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
||||
n = int(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
||||
if e1 != 0 {
|
||||
|
||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(events) > 0 {
|
||||
_p0 = unsafe.Pointer(&events[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_EPOLL_PWAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
||||
n = int(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
||||
if e1 != 0 {
|
||||
|
||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(events) > 0 {
|
||||
_p0 = unsafe.Pointer(&events[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
||||
n = int(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||
_, _, e1 := Syscall9(SYS_FADVISE64, uintptr(fd), 0, uintptr(offset>>32), uintptr(offset), uintptr(length>>32), uintptr(length), uintptr(advice), 0, 0)
|
||||
if e1 != 0 {
|
||||
|
||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(events) > 0 {
|
||||
_p0 = unsafe.Pointer(&events[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
||||
n = int(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
||||
if e1 != 0 {
|
||||
|
||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(events) > 0 {
|
||||
_p0 = unsafe.Pointer(&events[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
||||
n = int(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
||||
if e1 != 0 {
|
||||
|
||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(events) > 0 {
|
||||
_p0 = unsafe.Pointer(&events[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
||||
n = int(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||
_, _, e1 := Syscall9(SYS_FADVISE64, uintptr(fd), 0, uintptr(offset), uintptr(offset>>32), uintptr(length), uintptr(length>>32), uintptr(advice), 0, 0)
|
||||
if e1 != 0 {
|
||||
|
||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(events) > 0 {
|
||||
_p0 = unsafe.Pointer(&events[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
||||
n = int(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fchown(fd int, uid int, gid int) (err error) {
|
||||
_, _, e1 := Syscall(SYS_FCHOWN, uintptr(fd), uintptr(uid), uintptr(gid))
|
||||
if e1 != 0 {
|
||||
|
||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(events) > 0 {
|
||||
_p0 = unsafe.Pointer(&events[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
||||
n = int(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
||||
if e1 != 0 {
|
||||
|
||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(events) > 0 {
|
||||
_p0 = unsafe.Pointer(&events[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
||||
n = int(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
||||
if e1 != 0 {
|
||||
|
||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(events) > 0 {
|
||||
_p0 = unsafe.Pointer(&events[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_EPOLL_PWAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
||||
n = int(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
||||
if e1 != 0 {
|
||||
|
||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(events) > 0 {
|
||||
_p0 = unsafe.Pointer(&events[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
||||
n = int(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
||||
if e1 != 0 {
|
||||
|
||||
-17
@@ -45,23 +45,6 @@ func Tee(rfd int, wfd int, len int, flags int) (n int64, err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func EpollWait(epfd int, events []EpollEvent, msec int) (n int, err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(events) > 0 {
|
||||
_p0 = unsafe.Pointer(&events[0])
|
||||
} else {
|
||||
_p0 = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_EPOLL_WAIT, uintptr(epfd), uintptr(_p0), uintptr(len(events)), uintptr(msec), 0, 0)
|
||||
n = int(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Fadvise(fd int, offset int64, length int64, advice int) (err error) {
|
||||
_, _, e1 := Syscall6(SYS_FADVISE64, uintptr(fd), uintptr(offset), uintptr(length), uintptr(advice), 0, 0)
|
||||
if e1 != 0 {
|
||||
|
||||
+36
@@ -1109,17 +1109,53 @@ const (
|
||||
)
|
||||
|
||||
// This type is the union inside of TRUSTEE and must be created using one of the TrusteeValueFrom* functions.
|
||||
//
|
||||
// Go pointers stored in a TrusteeValue must be pinned using [runtime.Pinner]
|
||||
// for the lifetime of the TrusteeValue.
|
||||
type TrusteeValue uintptr
|
||||
|
||||
// TrusteeValueFromString is unsafe and should not be used.
|
||||
//
|
||||
// It returns a uintptr containing a reference to newly-allocated memory
|
||||
// which will be freed by the garbage collector.
|
||||
// There is no way for the caller to safely reference this memory.
|
||||
//
|
||||
// To create a [TrusteeValue] from a string, use:
|
||||
//
|
||||
// p, err := windows.UTF16PtrFromString(s)
|
||||
// if err != nil {
|
||||
// // handle error
|
||||
// }
|
||||
//
|
||||
// // Pin the string for as long as it is used.
|
||||
// var pinner runtime.Pinner
|
||||
// pinner.Pin(p)
|
||||
// defer pinner.Unpin()
|
||||
//
|
||||
// tv := TrusteeValue(unsafe.Pointer(p))
|
||||
//
|
||||
// Deprecated: TrusteeValueFromString is unsafe and should not be used.
|
||||
func TrusteeValueFromString(str string) TrusteeValue {
|
||||
return TrusteeValue(unsafe.Pointer(StringToUTF16Ptr(str)))
|
||||
}
|
||||
|
||||
// TrusteeValueFromSID returns a [TrusteeValue] referencing sid.
|
||||
//
|
||||
// The caller must pin sid using a [runtime.Pinner] for the lifetime of the TrusteeValue.
|
||||
func TrusteeValueFromSID(sid *SID) TrusteeValue {
|
||||
return TrusteeValue(unsafe.Pointer(sid))
|
||||
}
|
||||
|
||||
// TrusteeValueFromObjectsAndSid returns a [TrusteeValue] referencing objectsAndSid.
|
||||
//
|
||||
// The caller must pin objectsAndSid using a [runtime.Pinner] for the lifetime of the TrusteeValue.
|
||||
func TrusteeValueFromObjectsAndSid(objectsAndSid *OBJECTS_AND_SID) TrusteeValue {
|
||||
return TrusteeValue(unsafe.Pointer(objectsAndSid))
|
||||
}
|
||||
|
||||
// TrusteeValueFromObjectsAndName returns a [TrusteeValue] referencing objectsAndName.
|
||||
//
|
||||
// The caller must pin objectsAndName using a [runtime.Pinner] for the lifetime of the TrusteeValue.
|
||||
func TrusteeValueFromObjectsAndName(objectsAndName *OBJECTS_AND_NAME) TrusteeValue {
|
||||
return TrusteeValue(unsafe.Pointer(objectsAndName))
|
||||
}
|
||||
|
||||
+6
-2
@@ -1728,11 +1728,15 @@ func (s *NTUnicodeString) String() string {
|
||||
// the more common *uint16 string type.
|
||||
func NewNTString(s string) (*NTString, error) {
|
||||
var nts NTString
|
||||
s8, err := BytePtrFromString(s)
|
||||
s8, err := ByteSliceFromString(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
RtlInitString(&nts, s8)
|
||||
// The source string plus its terminating NUL must fit within MAX_USHORT.
|
||||
if len(s8) > MAX_USHORT {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
RtlInitString(&nts, &s8[0])
|
||||
return &nts, nil
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -169,6 +169,7 @@ const (
|
||||
FORMAT_MESSAGE_ARGUMENT_ARRAY = 8192
|
||||
FORMAT_MESSAGE_MAX_WIDTH_MASK = 255
|
||||
|
||||
MAX_USHORT = 0xffff
|
||||
MAX_PATH = 260
|
||||
MAX_LONG_PATH = 32768
|
||||
|
||||
|
||||
Vendored
+2
-2
@@ -700,7 +700,7 @@ golang.org/x/net/internal/socks
|
||||
golang.org/x/net/internal/timeseries
|
||||
golang.org/x/net/proxy
|
||||
golang.org/x/net/trace
|
||||
# golang.org/x/sys v0.46.0
|
||||
# golang.org/x/sys v0.47.0
|
||||
## explicit; go 1.25.0
|
||||
golang.org/x/sys/cpu
|
||||
golang.org/x/sys/execabs
|
||||
@@ -708,7 +708,7 @@ golang.org/x/sys/plan9
|
||||
golang.org/x/sys/unix
|
||||
golang.org/x/sys/windows
|
||||
golang.org/x/sys/windows/registry
|
||||
# golang.org/x/term v0.44.0
|
||||
# golang.org/x/term v0.45.0
|
||||
## explicit; go 1.25.0
|
||||
golang.org/x/term
|
||||
# golang.org/x/text v0.36.0
|
||||
|
||||
Reference in New Issue
Block a user