Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions cmd/hyard/cli/cli_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5106,6 +5106,114 @@ func TestHyardCloneGitLocatorPackageCoordinateRecordsProvenanceJSON(t *testing.T
require.Equal(t, payload.Source.Commit, record.Template.TemplateCommit)
}

func TestHyardClonePackageHandleCoordinateFromGitRegistry(t *testing.T) {
sourceRepo := seedHyardCloneHarnessTemplateSourceRepo(t)
sourceRemote := testutil.NewBareRemoteFromRepo(t, sourceRepo)
sourceCommit := sourceRepo.RevParse(t, "harness-template/workspace")

registryRepo := testutil.NewRepo(t)
registryRepo.Run(t, "branch", "-m", "main")
registryRepo.WriteFile(t, "curated/index.yaml", ""+
"schema_version: 1\n"+
"curated:\n"+
" workspace:\n"+
" target: acme/workspace\n")
registryRepo.WriteFile(t, "packages/acme/index.yaml", fmt.Sprintf(""+
"schema_version: 1\n"+
"namespace: acme\n"+
"packages:\n"+
" workspace:\n"+
" handle: acme/workspace\n"+
" status: active\n"+
" package:\n"+
" type: harness\n"+
" name: workspace\n"+
" source:\n"+
" repository: %q\n"+
" dist_tags:\n"+
" latest: \"0.1.0\"\n"+
" versions:\n"+
" \"0.1.0\":\n"+
" locator:\n"+
" kind: git\n"+
" repository: %q\n"+
" ref: harness-template/workspace\n"+
" commit: %s\n", sourceRemote, sourceRemote, sourceCommit))
registryRepo.AddAndCommit(t, "seed clone package registry")
registryRemote := testutil.NewBareRemoteFromRepo(t, registryRepo)
parentDir := t.TempDir()

lockHyardProcessEnv(t)
t.Setenv("HYARD_CACHE_DIR", t.TempDir())

stdout, stderr, err := executeHyardCLIUnlocked(
t,
parentDir,
"clone",
"Workspace",
"--registry-source",
registryRemote,
"--json",
)
require.NoError(t, err)
require.Empty(t, stderr)

var payload struct {
HarnessRoot string `json:"harness_root"`
HarnessID string `json:"harness_id"`
Source struct {
Repo string `json:"repo"`
RequestedRef string `json:"requested_ref"`
ResolvedRef string `json:"resolved_ref"`
Commit string `json:"commit"`
PackageName string `json:"package_name"`
PackageCoordinate string `json:"package_coordinate"`
PackageLocatorKind string `json:"package_locator_kind"`
PackageLocator string `json:"package_locator"`
RegistryProvenance struct {
RequestedCoordinate string `json:"requested_coordinate"`
ResolvedCoordinate string `json:"resolved_coordinate"`
ResolvedVersion string `json:"resolved_version"`
RegistryRemote string `json:"registry_remote"`
RegistryRef string `json:"registry_ref"`
PackageType string `json:"package_type"`
PackageIdentity string `json:"package_identity"`
SourceRemote string `json:"source_remote"`
SourceRef string `json:"source_ref"`
SourceCommit string `json:"source_commit"`
} `json:"registry_provenance"`
} `json:"source"`
}
require.NoError(t, json.Unmarshal([]byte(stdout), &payload))
require.Equal(t, gitpkg.ComparablePath(filepath.Join(parentDir, "workspace")), gitpkg.ComparablePath(payload.HarnessRoot))
require.NotEmpty(t, payload.HarnessID)
require.Equal(t, gitpkg.ComparablePath(sourceRemote), gitpkg.ComparablePath(payload.Source.Repo))
require.Equal(t, sourceCommit, payload.Source.RequestedRef)
require.Equal(t, sourceCommit, payload.Source.ResolvedRef)
require.Equal(t, sourceCommit, payload.Source.Commit)
require.Equal(t, "workspace", payload.Source.PackageName)
require.Equal(t, "workspace@latest", payload.Source.PackageCoordinate)
require.Equal(t, "git", payload.Source.PackageLocatorKind)
require.Contains(t, payload.Source.PackageLocator, sourceCommit)
require.Equal(t, "workspace@latest", payload.Source.RegistryProvenance.RequestedCoordinate)
require.Equal(t, "acme/workspace@0.1.0", payload.Source.RegistryProvenance.ResolvedCoordinate)
require.Equal(t, "0.1.0", payload.Source.RegistryProvenance.ResolvedVersion)
require.Equal(t, gitpkg.ComparablePath(registryRemote), gitpkg.ComparablePath(payload.Source.RegistryProvenance.RegistryRemote))
require.Equal(t, "HEAD", payload.Source.RegistryProvenance.RegistryRef)
require.Equal(t, "harness", payload.Source.RegistryProvenance.PackageType)
require.Equal(t, "workspace", payload.Source.RegistryProvenance.PackageIdentity)
require.Equal(t, gitpkg.ComparablePath(sourceRemote), gitpkg.ComparablePath(payload.Source.RegistryProvenance.SourceRemote))
require.Equal(t, "harness-template/workspace", payload.Source.RegistryProvenance.SourceRef)
require.Equal(t, sourceCommit, payload.Source.RegistryProvenance.SourceCommit)

record, err := harnesspkg.LoadBundleRecord(payload.HarnessRoot, payload.HarnessID)
require.NoError(t, err)
require.NotNil(t, record.Registry)
require.Equal(t, "workspace@latest", record.Registry.RequestedCoordinate)
require.Equal(t, "acme/workspace@0.1.0", record.Registry.ResolvedCoordinate)
require.Equal(t, sourceCommit, record.Registry.SourceCommit)
}

func TestHyardCloneRejectsInvalidHarnessTemplateSourceWithoutCreatingRepo(t *testing.T) {
t.Parallel()

Expand Down
158 changes: 145 additions & 13 deletions cmd/hyard/cli/clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
gitpkg "github.com/zack-nova/harnessyard/cmd/orbit/cli/git"
harnesspkg "github.com/zack-nova/harnessyard/cmd/orbit/cli/harness"
"github.com/zack-nova/harnessyard/cmd/orbit/cli/ids"
"github.com/zack-nova/harnessyard/cmd/orbit/cli/registry"
orbittemplate "github.com/zack-nova/harnessyard/cmd/orbit/cli/template"
)

Expand All @@ -27,15 +28,32 @@ const hyardStartLauncherContextKey hyardContextKey = "hyard_start_launcher"
const hyardViewRunInteractiveContextKey hyardContextKey = "hyard_view_run_interactive"

type cloneSourceJSON struct {
Kind string `json:"kind"`
Repo string `json:"repo"`
RequestedRef string `json:"requested_ref,omitempty"`
ResolvedRef string `json:"resolved_ref"`
Commit string `json:"commit"`
PackageName string `json:"package_name,omitempty"`
PackageCoordinate string `json:"package_coordinate,omitempty"`
PackageLocatorKind string `json:"package_locator_kind,omitempty"`
PackageLocator string `json:"package_locator,omitempty"`
Kind string `json:"kind"`
Repo string `json:"repo"`
RequestedRef string `json:"requested_ref,omitempty"`
ResolvedRef string `json:"resolved_ref"`
Commit string `json:"commit"`
PackageName string `json:"package_name,omitempty"`
PackageCoordinate string `json:"package_coordinate,omitempty"`
PackageLocatorKind string `json:"package_locator_kind,omitempty"`
PackageLocator string `json:"package_locator,omitempty"`
RegistryProvenance *cloneRegistryProvenanceJSON `json:"registry_provenance,omitempty"`
}

type cloneRegistryProvenanceJSON struct {
RequestedCoordinate string `json:"requested_coordinate"`
ResolvedCoordinate string `json:"resolved_coordinate"`
ResolvedVersion string `json:"resolved_version"`
RegistryRemote string `json:"registry_remote"`
RegistryRef string `json:"registry_ref"`
PackageType string `json:"package_type"`
PackageIdentity string `json:"package_identity"`
PackageStatus string `json:"package_status,omitempty"`
SourceRemote string `json:"source_remote"`
SourceRef string `json:"source_ref"`
SourceCommit string `json:"source_commit"`
CacheUsed bool `json:"cache_used"`
CacheStale bool `json:"cache_stale"`
}

type cloneNextActionJSON struct {
Expand Down Expand Up @@ -79,12 +97,14 @@ func hyardViewRunInteractiveFromContext(ctx context.Context) bool {

func newCloneCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "clone <harness-template-source> [repo-name]",
Short: "Bootstrap a new runtime repo from a harness template source",
Long: "Bootstrap a new runtime repo from a harness template source.\n" +
Use: "clone <harness-package-handle|harness-template-source> [repo-name]",
Short: "Bootstrap a new runtime repo from a harness package handle or template source",
Long: "Bootstrap a new runtime repo from a harness package handle or template source.\n" +
"This command is a controlled compose of runtime create plus harness template install,\n" +
"reusing the existing runtime bootstrap, install preview, provenance, and readiness helpers.",
Example: "" +
" hyard clone product-lab\n" +
" hyard clone zack-nova/product-lab demo-runtime\n" +
" hyard clone ../starter-template --ref harness-template/workspace\n" +
" hyard clone https://example.com/acme/starter.git demo --ref harness-template/workspace\n" +
" hyard clone git@github.com:acme/starter.git --path ../workspaces --ref harness-template/workspace\n",
Expand All @@ -100,7 +120,19 @@ func newCloneCommand() *cobra.Command {
return fmt.Errorf("read --ref flag: %w", err)
}
sourceMetadata := packageMetadata{}
if shouldParseHyardPackageCoordinateArg(sourceArg) {
defaultRepoName := ""
var registryProvenance *orbittemplate.InstallRegistryProvenance
if registry.LooksPackageHandleCoordinate(sourceArg) {
resolution, err := resolvePackageHandleCloneCoordinate(cmd, sourceArg)
if err != nil {
return err
}
sourceArg = resolution.SourceRemote
requestedRef = resolution.SourceCommit
sourceMetadata = packageMetadataFromRegistryResolution(resolution)
registryProvenance = installRegistryProvenanceFromRegistryResolution(resolution)
defaultRepoName = resolution.Coordinate.Name
} else if shouldParseHyardPackageCoordinateArg(sourceArg) {
coordinate, err := parseHyardPackageCoordinate(sourceArg)
if err != nil {
return err
Expand Down Expand Up @@ -141,6 +173,9 @@ func newCloneCommand() *cobra.Command {
return err
}
}
if repoName == "" {
repoName = defaultRepoName
}
if repoName == "" {
repoName, err = inferCloneRepoName(candidate.RepoURL)
if err != nil {
Expand Down Expand Up @@ -171,6 +206,7 @@ func newCloneCommand() *cobra.Command {
InstallSource: installSource,
RequireResolvedBindings: true,
Now: time.Now().UTC(),
Registry: registryProvenance,
})
if err != nil {
return fmt.Errorf("build harness template install preview: %w", err)
Expand Down Expand Up @@ -203,6 +239,7 @@ func newCloneCommand() *cobra.Command {
PackageCoordinate: sourceMetadata.coordinate,
PackageLocatorKind: sourceMetadata.locatorKind,
PackageLocator: sourceMetadata.locator,
RegistryProvenance: cloneRegistryProvenancePayload(registryProvenance),
},
MemberIDs: source.MemberIDs(),
MemberCount: len(source.MemberIDs()),
Expand Down Expand Up @@ -240,11 +277,106 @@ func newCloneCommand() *cobra.Command {

cmd.Flags().String("ref", "", "Install one explicit harness template branch from the source repository")
cmd.Flags().String("path", "", "Create the new runtime repo under this parent directory")
cmd.Flags().String("registry-source", "", "Git remote or local path for Package Handle Coordinate registry source")
cmd.Flags().String("registry-ref", registry.DefaultRegistryRef, "Git ref to read from the Package Handle Coordinate registry source")
cmd.Flags().Bool("allow-yanked", false, "Allow cloning a yanked Harness Package handle from the registry")
cmd.Flags().Bool("json", false, "Output machine-readable JSON")

return cmd
}

func resolvePackageHandleCloneCoordinate(cmd *cobra.Command, raw string) (registry.Resolution, error) {
coordinate, err := registry.ParsePackageHandleCoordinate(raw)
if err != nil {
return registry.Resolution{}, fmt.Errorf("parse package handle coordinate: %w", err)
}
if cmd.Flags().Changed("ref") {
return registry.Resolution{}, fmt.Errorf("package handle coordinate %s cannot be combined with --ref; registry versions resolve their source ref from catalog data", coordinate.String())
}

registrySource, err := packageRegistrySourceFromCommand(cmd)
if err != nil {
return registry.Resolution{}, err
}
cacheRoot, err := registry.DefaultCacheRoot()
if err != nil {
return registry.Resolution{}, fmt.Errorf("resolve registry cache root: %w", err)
}
scratchRoot, err := os.MkdirTemp("", "hyard-clone-registry-*")
if err != nil {
return registry.Resolution{}, fmt.Errorf("create clone registry scratch repo: %w", err)
}
defer os.RemoveAll(scratchRoot)

if _, err := gitpkg.EnsureRepoRoot(cmd.Context(), scratchRoot); err != nil {
return registry.Resolution{}, fmt.Errorf("initialize clone registry scratch repo: %w", err)
}

resolution, err := registry.ResolvePackageHandleCoordinate(cmd.Context(), registry.ResolveInput{
RepoRoot: scratchRoot,
Coordinate: coordinate,
RegistrySource: registrySource,
CacheRoot: cacheRoot,
})
if err != nil {
return registry.Resolution{}, fmt.Errorf("resolve package handle coordinate: %w", err)
}
allowYanked, err := cmd.Flags().GetBool("allow-yanked")
if err != nil {
return registry.Resolution{}, fmt.Errorf("read --allow-yanked flag: %w", err)
}
if err := registry.RequireInstallableResolution(resolution, registry.InstallGateOptions{AllowYanked: allowYanked}); err != nil {
return registry.Resolution{}, fmt.Errorf("check registry package status: %w", err)
}
if resolution.PackageType != ids.PackageTypeHarness {
return registry.Resolution{}, fmt.Errorf("hyard clone package handle %s resolved to %s package %q; use `hyard install %s` inside an existing runtime", resolution.Coordinate.String(), resolution.PackageType, resolution.PackageIdentity, resolution.Coordinate.String())
}

return resolution, nil
}

func installRegistryProvenanceFromRegistryResolution(resolution registry.Resolution) *orbittemplate.InstallRegistryProvenance {
exactCoordinate := resolution.ExactCoordinate()
cacheStale := resolution.FromCache && !resolution.Coordinate.IsExactVersion()
return &orbittemplate.InstallRegistryProvenance{
RequestedCoordinate: resolution.Coordinate.String(),
ResolvedCoordinate: exactCoordinate.String(),
ResolvedVersion: exactCoordinate.Version,
RegistryRemote: resolution.RegistryRemote,
RegistryRef: resolution.RegistryRef,
PackageType: resolution.PackageType,
PackageIdentity: resolution.PackageIdentity,
PackageStatus: string(resolution.EffectivePackageStatus()),
SourceRemote: resolution.SourceRemote,
SourceRef: resolution.SourceRef,
SourceCommit: resolution.SourceCommit,
CacheUsed: resolution.FromCache,
CacheStale: cacheStale,
}
}

func cloneRegistryProvenancePayload(provenance *orbittemplate.InstallRegistryProvenance) *cloneRegistryProvenanceJSON {
if provenance == nil {
return nil
}

return &cloneRegistryProvenanceJSON{
RequestedCoordinate: provenance.RequestedCoordinate,
ResolvedCoordinate: provenance.ResolvedCoordinate,
ResolvedVersion: provenance.ResolvedVersion,
RegistryRemote: provenance.RegistryRemote,
RegistryRef: provenance.RegistryRef,
PackageType: provenance.PackageType,
PackageIdentity: provenance.PackageIdentity,
PackageStatus: provenance.PackageStatus,
SourceRemote: provenance.SourceRemote,
SourceRef: provenance.SourceRef,
SourceCommit: provenance.SourceCommit,
CacheUsed: provenance.CacheUsed,
CacheStale: provenance.CacheStale,
}
}

func cloneHarnessStartNextActions(harnessRoot string) []cloneNextActionJSON {
return []cloneNextActionJSON{{
Kind: "harness_start",
Expand Down
33 changes: 22 additions & 11 deletions cmd/hyard/cli/install_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,20 @@ func TestHyardInstallExactPackageHandleCoordinateFromGitRegistry(t *testing.T) {
"namespace: acme\n"+
"packages:\n"+
" docs:\n"+
" handle: acme/docs\n"+
" status: active\n"+
" package:\n"+
" type: orbit\n"+
" name: docs\n"+
" source:\n"+
" repository: %q\n"+
" versions:\n"+
" 0.1.0:\n"+
" package_type: orbit\n"+
" package_identity: docs\n"+
" source:\n"+
" remote: %q\n"+
" locator:\n"+
" kind: git\n"+
" repository: %q\n"+
" ref: orbit-template/docs\n"+
" commit: %s\n", sourceRemote, sourceCommit))
" commit: %s\n", sourceRemote, sourceRemote, sourceCommit))
registryRepo.AddAndCommit(t, "seed package registry")
registryRemote := testutil.NewBareRemoteFromRepo(t, registryRepo)

Expand Down Expand Up @@ -572,23 +577,29 @@ func seedPackageHandleInstallFixture(t *testing.T, packageStatus string) package
"schema_version: 1\n"+
"curated:\n"+
" docs:\n"+
" target: acme/docs\n")
" target: acme/docs\n"+
" status: active\n")
registryRepo.WriteFile(t, "packages/acme/index.yaml", fmt.Sprintf(""+
"schema_version: 1\n"+
"namespace: acme\n"+
"packages:\n"+
" docs:\n"+
" handle: acme/docs\n"+
" status: %s\n"+
" package:\n"+
" type: orbit\n"+
" name: docs\n"+
" source:\n"+
" repository: %q\n"+
" dist_tags:\n"+
" latest: 0.1.0\n"+
" versions:\n"+
" 0.1.0:\n"+
" package_type: orbit\n"+
" package_identity: docs\n"+
" source:\n"+
" remote: %q\n"+
" locator:\n"+
" kind: git\n"+
" repository: %q\n"+
" ref: orbit-template/docs\n"+
" commit: %s\n", packageStatus, sourceRemote, sourceCommit))
" commit: %s\n", packageStatus, sourceRemote, sourceRemote, sourceCommit))
registryRepo.AddAndCommit(t, "seed package registry")
registryRemote := testutil.NewBareRemoteFromRepo(t, registryRepo)

Expand Down
Loading
Loading