this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

cmd/cue: cue mod upload command

This adds an experimental (and perhaps temporary) command
that can be used to upload a module to an OCI registry.

To repeat: this command is EXPERIMENTAL; usage and
name will change in the future.

Signed-off-by: Roger Peppe <rogpeppe@gmail.com>
Change-Id: I5aa976b528a1d7c9ae50335adb15921cdf8e5465
Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1171801
TryBot-Result: CUEcueckoo <cueckoo@cuelang.org>
Unity-Result: CUE porcuepine <cue.porcuepine@gmail.com>
Reviewed-by: Daniel Martí <mvdan@mvdan.cc>

+320 -11
+7 -7
cmd/cue/cmd/mod.go
··· 45 45 } 46 46 47 47 cmd.AddCommand(newModInitCmd(c)) 48 + cmd.AddCommand(newModUploadCmd(c)) 48 49 return cmd 49 50 } 50 51 ··· 52 53 cmd := &cobra.Command{ 53 54 Use: "init [module]", 54 55 Short: "initialize new module in current directory", 55 - Long: `Init initializes a cue.mod directory in the current directory, 56 - in effect creating a new module rooted at the current directory. 57 - The cue.mod directory must not already exist. 58 - A legacy cue.mod file in the current directory is moved 59 - to the new subdirectory. 56 + Long: `Init initializes a cue.mod directory in the current directory, in effect 57 + creating a new module rooted at the current directory. The cue.mod 58 + directory must not already exist. A legacy cue.mod file in the current 59 + directory is moved to the new subdirectory. 60 60 61 - A module name is optional, but if it is not given a packages 62 - within the module cannot imported another package defined 61 + A module name is optional, but if it is not given, a package 62 + within the module cannot import another package defined 63 63 in the module. 64 64 `, 65 65 RunE: mkRunE(c, runModInit),
+96
cmd/cue/cmd/modupload.go
··· 1 + // Copyright 2023 The CUE Authors 2 + // 3 + // Licensed under the Apache License, Version 2.0 (the "License"); 4 + // you may not use this file except in compliance with the License. 5 + // You may obtain a copy of the License at 6 + // 7 + // http://www.apache.org/licenses/LICENSE-2.0 8 + // 9 + // Unless required by applicable law or agreed to in writing, software 10 + // distributed under the License is distributed on an "AS IS" BASIS, 11 + // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 + // See the License for the specific language governing permissions and 13 + // limitations under the License. 14 + 15 + package cmd 16 + 17 + import ( 18 + "context" 19 + "fmt" 20 + "os" 21 + 22 + "github.com/spf13/cobra" 23 + 24 + "cuelang.org/go/internal/mod/modfile" 25 + "cuelang.org/go/internal/mod/modregistry" 26 + "cuelang.org/go/internal/mod/module" 27 + "cuelang.org/go/internal/mod/modzip" 28 + ) 29 + 30 + func newModUploadCmd(c *Command) *cobra.Command { 31 + cmd := &cobra.Command{ 32 + // TODO: this command is still experimental, don't show it in 33 + // the documentation just yet. 34 + Hidden: true, 35 + 36 + Use: "upload <version>", 37 + Short: "upload the current module to a registry", 38 + Long: `WARNING: THIS COMMAND IS EXPERIMENTAL. 39 + 40 + Upload the current module to an OCI registry. 41 + Currently this command must be run in the module's root directory. 42 + Also note that this command does no dependency or other checks at the moment. 43 + `, 44 + RunE: mkRunE(c, runModUpload), 45 + Args: cobra.ExactArgs(1), 46 + } 47 + 48 + return cmd 49 + } 50 + 51 + func runModUpload(cmd *Command, args []string) error { 52 + reg, err := getRegistry() 53 + if err != nil { 54 + return err 55 + } 56 + if reg == nil { 57 + return fmt.Errorf("no registry configured to upload to") 58 + } 59 + modfileData, err := os.ReadFile("cue.mod/module.cue") 60 + if err != nil { 61 + if os.IsNotExist(err) { 62 + return fmt.Errorf("no cue.mod/module.cue file found; cue mod upload must be run in the module's root directory") 63 + } 64 + return err 65 + } 66 + mf, err := modfile.Parse(modfileData, "cue.mod/module.cue") 67 + if err != nil { 68 + return err 69 + } 70 + mv, err := module.NewVersion(mf.Module, args[0]) 71 + if err != nil { 72 + return fmt.Errorf("cannot form module version: %v", err) 73 + } 74 + zf, err := os.CreateTemp("", "cue-upload-") 75 + if err != nil { 76 + return err 77 + } 78 + defer os.Remove(zf.Name()) 79 + defer zf.Close() 80 + 81 + // TODO verify that all dependencies exist in the registry. 82 + if err := modzip.CreateFromDir(zf, mv, "."); err != nil { 83 + return err 84 + } 85 + info, err := zf.Stat() 86 + if err != nil { 87 + return err 88 + } 89 + 90 + rclient := modregistry.NewClient(reg) 91 + if err := rclient.PutModule(context.Background(), mv, zf, info.Size()); err != nil { 92 + return fmt.Errorf("cannot put module: %v", err) 93 + } 94 + fmt.Printf("uploaded %s\n", mv) 95 + return nil 96 + }
+38
cmd/cue/cmd/script_test.go
··· 20 20 "context" 21 21 "fmt" 22 22 "io/fs" 23 + "net/http/httptest" 24 + "net/url" 23 25 "os" 24 26 "path" 25 27 "path/filepath" ··· 28 30 "testing" 29 31 "time" 30 32 33 + "cuelabs.dev/go/oci/ociregistry/ocimem" 34 + "cuelabs.dev/go/oci/ociregistry/ociserver" 31 35 "github.com/google/shlex" 32 36 "github.com/rogpeppe/go-internal/goproxytest" 33 37 "github.com/rogpeppe/go-internal/gotooltest" ··· 108 112 data = tsExpand(ts, data) 109 113 ts.Check(os.WriteFile(path, []byte(data), 0o666)) 110 114 } 115 + }, 116 + // memregistry starts an in-memory OCI server and sets the argument 117 + // environment variable name to its hostname. 118 + "memregistry": func(ts *testscript.TestScript, neg bool, args []string) { 119 + usage := func() { 120 + ts.Fatalf("usage: memregistry [-auth=username:password] <envvar-name>") 121 + } 122 + if neg { 123 + usage() 124 + } 125 + var auth *registrytest.AuthConfig 126 + if len(args) > 0 && strings.HasPrefix(args[0], "-") { 127 + userPass, ok := strings.CutPrefix(args[0], "-auth=") 128 + if !ok { 129 + usage() 130 + } 131 + user, pass, ok := strings.Cut(userPass, ":") 132 + if !ok { 133 + usage() 134 + } 135 + auth = &registrytest.AuthConfig{ 136 + Username: user, 137 + Password: pass, 138 + } 139 + args = args[1:] 140 + } 141 + if len(args) != 1 { 142 + usage() 143 + } 144 + 145 + srv := httptest.NewServer(registrytest.AuthHandler(ociserver.New(ocimem.New(), nil), auth)) 146 + u, _ := url.Parse(srv.URL) 147 + ts.Setenv(args[0], u.Host) 148 + ts.Defer(srv.Close) 111 149 }, 112 150 }, 113 151 Setup: func(e *testscript.Env) error {
+122
cmd/cue/cmd/testdata/script/registry_upload.txtar
··· 1 + # Check that we can use the cue mod upload command to upload to a registry. 2 + memregistry MEMREGISTRY 3 + env ORIG_CUE_REGISTRY=$CUE_REGISTRY 4 + env CUE_REGISTRY=example.com=$MEMREGISTRY+insecure,$CUE_REGISTRY 5 + cd example 6 + exec cue mod upload v0.0.1 7 + cmp stdout ../expect-upload-stdout 8 + cd ../main 9 + exec cue eval . 10 + cmp stdout ../expect-eval-stdout 11 + 12 + # Sanity check that the module isn't present in the fallback registry. 13 + env CUE_REGISTRY=$ORIG_CUE_REGISTRY 14 + ! exec cue eval 15 + stderr 'repository name not known to registry' 16 + 17 + -- expect-upload-stdout -- 18 + uploaded example.com@v0.0.1 19 + -- expect-eval-stdout -- 20 + main: "main" 21 + "foo.com/bar/hello@v0": "v0.2.3" 22 + "bar.com@v0": "v0.5.0" 23 + "baz.org@v0": "v0.10.1" 24 + "example.com@v0": "v0.0.1" 25 + -- main/cue.mod/module.cue -- 26 + module: "main.org" 27 + 28 + deps: "example.com@v0": v: "v0.0.1" 29 + 30 + -- main/main.cue -- 31 + package main 32 + import "example.com@v0:main" 33 + 34 + main 35 + 36 + -- example/cue.mod/module.cue -- 37 + module: "example.com@v0" 38 + deps: { 39 + "foo.com/bar/hello@v0": v: "v0.2.3" 40 + "bar.com@v0": v: "v0.5.0" 41 + } 42 + 43 + -- example/top.cue -- 44 + package main 45 + 46 + // Note: import without a major version takes 47 + // the major version from the module.cue file. 48 + import a "foo.com/bar/hello" 49 + a 50 + main: "main" 51 + "example.com@v0": "v0.0.1" 52 + 53 + -- _registry/foo.com_bar_hello_v0.2.3/cue.mod/module.cue -- 54 + module: "foo.com/bar/hello@v0" 55 + deps: { 56 + "bar.com@v0": v: "v0.0.2" 57 + "baz.org@v0": v: "v0.10.1" 58 + } 59 + 60 + -- _registry/foo.com_bar_hello_v0.2.3/x.cue -- 61 + package hello 62 + import ( 63 + a "bar.com/bar@v0" 64 + b "baz.org@v0:baz" 65 + ) 66 + "foo.com/bar/hello@v0": "v0.2.3" 67 + a 68 + b 69 + 70 + 71 + -- _registry/bar.com_v0.0.2/cue.mod/module.cue -- 72 + module: "bar.com@v0" 73 + deps: "baz.org@v0": v: "v0.0.2" 74 + 75 + -- _registry/bar.com_v0.0.2/bar/x.cue -- 76 + package bar 77 + import a "baz.org@v0:baz" 78 + "bar.com@v0": "v0.0.2" 79 + a 80 + 81 + 82 + -- _registry/bar.com_v0.5.0/cue.mod/module.cue -- 83 + module: "bar.com@v0" 84 + deps: "baz.org@v0": v: "v0.5.0" 85 + 86 + -- _registry/bar.com_v0.5.0/bar/x.cue -- 87 + package bar 88 + import a "baz.org@v0:baz" 89 + "bar.com@v0": "v0.5.0" 90 + a 91 + 92 + 93 + -- _registry/baz.org_v0.0.2/cue.mod/module.cue -- 94 + module: "baz.org@v0" 95 + 96 + -- _registry/baz.org_v0.0.2/baz.cue -- 97 + package baz 98 + "baz.org@v0": "v0.0.2" 99 + 100 + 101 + -- _registry/baz.org_v0.1.2/cue.mod/module.cue -- 102 + module: "baz.org@v0" 103 + 104 + -- _registry/baz.org_v0.1.2/baz.cue -- 105 + package baz 106 + "baz.org@v0": "v0.1.2" 107 + 108 + 109 + -- _registry/baz.org_v0.5.0/cue.mod/module.cue -- 110 + module: "baz.org@v0" 111 + 112 + -- _registry/baz.org_v0.5.0/baz.cue -- 113 + package baz 114 + "baz.org@v0": "v0.5.0" 115 + 116 + 117 + -- _registry/baz.org_v0.10.1/cue.mod/module.cue -- 118 + module: "baz.org@v0" 119 + 120 + -- _registry/baz.org_v0.10.1/baz.cue -- 121 + package baz 122 + "baz.org@v0": "v0.10.1"
+49
cmd/cue/cmd/testdata/script/registry_upload_auth.txtar
··· 1 + # Check that we can use the cue mod upload command to upload to a registry 2 + # that's protected by authorization. 3 + 4 + memregistry -auth=foo:bar MEMREGISTRY 5 + env CUE_EXPERIMENT=modules 6 + env CUE_REGISTRY=$MEMREGISTRY+insecure 7 + env DOCKER_CONFIG=$WORK/dockerconfig 8 + env-fill $DOCKER_CONFIG/config.json 9 + 10 + cd example 11 + exec cue mod upload v0.0.1 12 + cmp stdout ../expect-upload-stdout 13 + cd ../main 14 + exec cue eval . 15 + cmp stdout ../expect-eval-stdout 16 + 17 + -- dockerconfig/config.json -- 18 + { 19 + "auths": { 20 + "${MEMREGISTRY}": { 21 + "username": "foo", 22 + "password": "bar" 23 + } 24 + } 25 + } 26 + 27 + -- expect-upload-stdout -- 28 + uploaded example.com@v0.0.1 29 + -- expect-eval-stdout -- 30 + main: "main" 31 + "example.com@v0": "v0.0.1" 32 + -- main/cue.mod/module.cue -- 33 + module: "main.org" 34 + deps: "example.com@v0": v: "v0.0.1" 35 + 36 + -- main/main.cue -- 37 + package main 38 + import "example.com@v0:main" 39 + 40 + main 41 + "main": "main" 42 + 43 + -- example/cue.mod/module.cue -- 44 + module: "example.com@v0" 45 + 46 + -- example/top.cue -- 47 + package main 48 + 49 + "example.com@v0": "v0.0.1"
+8 -4
internal/registrytest/registry.go
··· 60 60 if err := pushContent(client, mods); err != nil { 61 61 return nil, fmt.Errorf("cannot push modules: %v", err) 62 62 } 63 - var handler http.Handler = ociserver.New(r, nil) 63 + var handler http.Handler = ociserver.New(ocifilter.ReadOnly(r), nil) 64 64 if authConfigData != nil { 65 65 var cfg AuthConfig 66 66 if err := json.Unmarshal(authConfigData, &cfg); err != nil { 67 67 return nil, fmt.Errorf("invalid auth.json: %v", err) 68 68 } 69 - handler = authMiddleware(handler, &cfg) 69 + handler = AuthHandler(handler, &cfg) 70 70 } 71 71 srv := httptest.NewServer(handler) 72 72 u, err := url.Parse(srv.URL) ··· 79 79 }, nil 80 80 } 81 81 82 - func authMiddleware(handler http.Handler, cfg *AuthConfig) http.Handler { 83 - if cfg.Username == "" { 82 + // AuthHandler wraps the given handler with logic that checks 83 + // that the incoming requests fulfil the auth requirements defined 84 + // in cfg. If cfg is nil or there are no auth requirements, it returns handler 85 + // unchanged. 86 + func AuthHandler(handler http.Handler, cfg *AuthConfig) http.Handler { 87 + if cfg == nil || cfg.Username == "" { 84 88 return handler 85 89 } 86 90 return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {