diff --git a/app/cli/cmd/attestation.go b/app/cli/cmd/attestation.go index c93865179..fdc4acb12 100644 --- a/app/cli/cmd/attestation.go +++ b/app/cli/cmd/attestation.go @@ -26,6 +26,7 @@ import ( var ( attAPIToken string useAttestationRemoteState bool + attestationLocalStatePath string GracefulExit bool // attestationID is the unique identifier of the in-progress attestation // this is required when use-attestation-remote-state is enabled @@ -69,6 +70,7 @@ func newAttestationCmd() *cobra.Command { cobra.CheckErr(cmd.PersistentFlags().MarkHidden("remote-state")) cmd.PersistentFlags().BoolVar(&GracefulExit, "graceful-exit", false, "exit 0 in case of error. NOTE: this flag will be removed once Chainloop reaches 1.0") + cmd.PersistentFlags().StringVar(&attestationLocalStatePath, "local-state-path", "", "path to store the attestation state locally, default: [tmpDir]/chainloop_attestation.tmp.json") cmd.AddCommand(newAttestationInitCmd(), newAttestationAddCmd(), newAttestationStatusCmd(), newAttestationPushCmd(), newAttestationResetCmd()) diff --git a/app/cli/cmd/attestation_add.go b/app/cli/cmd/attestation_add.go index 12d6f2234..6a3310d0a 100644 --- a/app/cli/cmd/attestation_add.go +++ b/app/cli/cmd/attestation_add.go @@ -72,6 +72,7 @@ func newAttestationAddCmd() *cobra.Command { RegistryServer: registryServer, RegistryUsername: registryUsername, RegistryPassword: registryPassword, + LocalStatePath: attestationLocalStatePath, }, ) if err != nil { diff --git a/app/cli/cmd/attestation_init.go b/app/cli/cmd/attestation_init.go index 48d106e33..ee8c5c215 100644 --- a/app/cli/cmd/attestation_init.go +++ b/app/cli/cmd/attestation_init.go @@ -74,6 +74,7 @@ func newAttestationInitCmd() *cobra.Command { DryRun: attestationDryRun, Force: force, UseRemoteState: useAttestationRemoteState, + LocalStatePath: attestationLocalStatePath, }, ) if err != nil { @@ -109,7 +110,7 @@ func newAttestationInitCmd() *cobra.Command { logger.Info().Msg("Attestation initialized! now you can check its status or add materials to it") // Show the status information - statusAction, err := action.NewAttestationStatus(&action.AttestationStatusOpts{ActionsOpts: actionOpts, UseAttestationRemoteState: useAttestationRemoteState}) + statusAction, err := action.NewAttestationStatus(&action.AttestationStatusOpts{ActionsOpts: actionOpts, UseAttestationRemoteState: useAttestationRemoteState, LocalStatePath: attestationLocalStatePath}) if err != nil { return newGracefulError(err) } diff --git a/app/cli/cmd/attestation_push.go b/app/cli/cmd/attestation_push.go index 8a46448e7..276fc1592 100644 --- a/app/cli/cmd/attestation_push.go +++ b/app/cli/cmd/attestation_push.go @@ -65,7 +65,7 @@ func newAttestationPushCmd() *cobra.Command { a, err := action.NewAttestationPush(&action.AttestationPushOpts{ ActionsOpts: actionOpts, KeyPath: pkPath, BundlePath: bundle, CLIVersion: info.Version, CLIDigest: info.Digest, - SignServerCAPath: signServerCAPath, + SignServerCAPath: signServerCAPath, LocalStatePath: attestationLocalStatePath, }) if err != nil { return fmt.Errorf("failed to load action: %w", err) diff --git a/app/cli/cmd/attestation_reset.go b/app/cli/cmd/attestation_reset.go index e1819a243..f791ec6eb 100644 --- a/app/cli/cmd/attestation_reset.go +++ b/app/cli/cmd/attestation_reset.go @@ -1,5 +1,5 @@ // -// Copyright 2023 The Chainloop Authors. +// Copyright 2024 The Chainloop Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -41,7 +41,7 @@ func newAttestationResetCmd() *cobra.Command { return nil }, RunE: func(cmd *cobra.Command, args []string) error { - a, err := action.NewAttestationReset(actionOpts) + a, err := action.NewAttestationReset(&action.AttestationResetOpts{ActionsOpts: actionOpts, LocalStatePath: attestationLocalStatePath}) if err != nil { return fmt.Errorf("failed to load action: %w", err) } diff --git a/app/cli/cmd/attestation_status.go b/app/cli/cmd/attestation_status.go index 1e3d586e7..9a80252dd 100644 --- a/app/cli/cmd/attestation_status.go +++ b/app/cli/cmd/attestation_status.go @@ -40,6 +40,7 @@ func newAttestationStatusCmd() *cobra.Command { &action.AttestationStatusOpts{ UseAttestationRemoteState: attestationID != "", ActionsOpts: actionOpts, + LocalStatePath: attestationLocalStatePath, }, ) if err != nil { diff --git a/app/cli/internal/action/action.go b/app/cli/internal/action/action.go index 64eaae373..123f0463f 100644 --- a/app/cli/internal/action/action.go +++ b/app/cli/internal/action/action.go @@ -46,25 +46,36 @@ func toTimePtr(t time.Time) *time.Time { } // load a crafter with either local or remote state -func newCrafter(enableRemoteState bool, conn *grpc.ClientConn, opts ...crafter.NewOpt) (*crafter.Crafter, error) { + +type newCrafterStateOpts struct { + enableRemoteState bool + localStatePath string +} + +func newCrafter(stateOpts *newCrafterStateOpts, conn *grpc.ClientConn, opts ...crafter.NewOpt) (*crafter.Crafter, error) { var stateManager crafter.StateManager var err error + if stateOpts == nil { + return nil, fmt.Errorf("missing state manager options") + } + // run opts to extract logger c := &crafter.Crafter{} for _, opt := range opts { _ = opt(c) } - switch enableRemoteState { + switch stateOpts.enableRemoteState { case true: stateManager, err = remote.New(pb.NewAttestationStateServiceClient(conn), c.Logger) case false: attestationStatePath := filepath.Join(os.TempDir(), "chainloop-attestation.tmp.json") - if path := os.Getenv("CHAINLOOP_ATTESTATION_STATE_PATH"); path != "" { + if path := stateOpts.localStatePath; path != "" { attestationStatePath = path } + c.Logger.Debug().Str("path", attestationStatePath).Msg("using local state") stateManager, err = filesystem.New(attestationStatePath) } diff --git a/app/cli/internal/action/attestation_add.go b/app/cli/internal/action/attestation_add.go index bdfa28206..c138d1abb 100644 --- a/app/cli/internal/action/attestation_add.go +++ b/app/cli/internal/action/attestation_add.go @@ -36,6 +36,7 @@ type AttestationAddOpts struct { ConnectionInsecure bool // OCI registry credentials used for CONTAINER_IMAGE material type RegistryServer, RegistryUsername, RegistryPassword string + LocalStatePath string } type newCrafterOpts struct { @@ -49,6 +50,7 @@ type AttestationAdd struct { // optional CA certificate for the CAS connection casCAPath string connectionInsecure bool + localStatePath string *newCrafterOpts } @@ -65,6 +67,7 @@ func NewAttestationAdd(cfg *AttestationAddOpts) (*AttestationAdd, error) { casURI: cfg.CASURI, casCAPath: cfg.CASCAPath, connectionInsecure: cfg.ConnectionInsecure, + localStatePath: cfg.LocalStatePath, }, nil } @@ -72,7 +75,7 @@ var ErrAttestationNotInitialized = errors.New("attestation not yet initialized") func (action *AttestationAdd) Run(ctx context.Context, attestationID, materialName, materialValue, materialType string, annotations map[string]string) error { // initialize the crafter. If attestation-id is provided we assume the attestation is performed using remote state - crafter, err := newCrafter(attestationID != "", action.CPConnection, action.newCrafterOpts.opts...) + crafter, err := newCrafter(&newCrafterStateOpts{enableRemoteState: (attestationID != ""), localStatePath: action.localStatePath}, action.CPConnection, action.newCrafterOpts.opts...) if err != nil { return fmt.Errorf("failed to load crafter: %w", err) } diff --git a/app/cli/internal/action/attestation_init.go b/app/cli/internal/action/attestation_init.go index 9f424d65d..d99ba566e 100644 --- a/app/cli/internal/action/attestation_init.go +++ b/app/cli/internal/action/attestation_init.go @@ -37,6 +37,7 @@ type AttestationInitOpts struct { // since it's a protection to make sure you don't override the state by mistake Force bool UseRemoteState bool + LocalStatePath string } type AttestationInit struct { @@ -58,7 +59,7 @@ func (e ErrRunnerContextNotFound) Error() string { } func NewAttestationInit(cfg *AttestationInitOpts) (*AttestationInit, error) { - c, err := newCrafter(cfg.UseRemoteState, cfg.CPConnection, crafter.WithLogger(&cfg.Logger)) + c, err := newCrafter(&newCrafterStateOpts{enableRemoteState: cfg.UseRemoteState, localStatePath: cfg.LocalStatePath}, cfg.CPConnection, crafter.WithLogger(&cfg.Logger)) if err != nil { return nil, fmt.Errorf("failed to load crafter: %w", err) } diff --git a/app/cli/internal/action/attestation_push.go b/app/cli/internal/action/attestation_push.go index fb22a6271..0fae323f3 100644 --- a/app/cli/internal/action/attestation_push.go +++ b/app/cli/internal/action/attestation_push.go @@ -35,6 +35,7 @@ type AttestationPushOpts struct { KeyPath, CLIVersion, CLIDigest, BundlePath string SignServerCAPath string + LocalStatePath string } type AttestationResult struct { @@ -47,6 +48,7 @@ type AttestationPush struct { *ActionsOpts keyPath, cliVersion, cliDigest, bundlePath string signServerCAPath string + localStatePath string *newCrafterOpts } @@ -59,6 +61,7 @@ func NewAttestationPush(cfg *AttestationPushOpts) (*AttestationPush, error) { cliDigest: cfg.CLIDigest, bundlePath: cfg.BundlePath, signServerCAPath: cfg.SignServerCAPath, + localStatePath: cfg.LocalStatePath, newCrafterOpts: &newCrafterOpts{cpConnection: cfg.CPConnection, opts: opts}, }, nil } @@ -66,7 +69,7 @@ func NewAttestationPush(cfg *AttestationPushOpts) (*AttestationPush, error) { func (action *AttestationPush) Run(ctx context.Context, attestationID string, runtimeAnnotations map[string]string) (*AttestationResult, error) { useRemoteState := attestationID != "" // initialize the crafter. If attestation-id is provided we assume the attestation is performed using remote state - crafter, err := newCrafter(useRemoteState, action.CPConnection, action.newCrafterOpts.opts...) + crafter, err := newCrafter(&newCrafterStateOpts{enableRemoteState: useRemoteState, localStatePath: action.localStatePath}, action.CPConnection, action.newCrafterOpts.opts...) if err != nil { return nil, fmt.Errorf("failed to load crafter: %w", err) } @@ -79,7 +82,7 @@ func (action *AttestationPush) Run(ctx context.Context, attestationID string, ru // Retrieve attestation status statusAction, err := NewAttestationStatus(&AttestationStatusOpts{ - ActionsOpts: action.ActionsOpts, UseAttestationRemoteState: useRemoteState, isPushed: true, + ActionsOpts: action.ActionsOpts, UseAttestationRemoteState: useRemoteState, isPushed: true, LocalStatePath: action.localStatePath, }) if err != nil { diff --git a/app/cli/internal/action/attestation_reset.go b/app/cli/internal/action/attestation_reset.go index 07837cfe4..321dfe641 100644 --- a/app/cli/internal/action/attestation_reset.go +++ b/app/cli/internal/action/attestation_reset.go @@ -29,22 +29,24 @@ const AttestationResetTriggerCancelled = "cancellation" type AttestationResetOpts struct { *ActionsOpts + LocalStatePath string } type AttestationReset struct { *ActionsOpts *newCrafterOpts + localStatePath string } -func NewAttestationReset(cfg *ActionsOpts) (*AttestationReset, error) { +func NewAttestationReset(cfg *AttestationResetOpts) (*AttestationReset, error) { return &AttestationReset{ newCrafterOpts: &newCrafterOpts{cpConnection: cfg.CPConnection, opts: []crafter.NewOpt{crafter.WithLogger(&cfg.Logger)}}, - ActionsOpts: cfg}, nil + ActionsOpts: cfg.ActionsOpts, localStatePath: cfg.LocalStatePath}, nil } func (action *AttestationReset) Run(ctx context.Context, attestationID, trigger, reason string) error { // initialize the crafter. If attestation-id is provided we assume the attestation is performed using remote state - crafter, err := newCrafter(attestationID != "", action.CPConnection, action.newCrafterOpts.opts...) + crafter, err := newCrafter(&newCrafterStateOpts{enableRemoteState: attestationID != "", localStatePath: action.localStatePath}, action.CPConnection, action.newCrafterOpts.opts...) if err != nil { return fmt.Errorf("failed to load crafter: %w", err) } diff --git a/app/cli/internal/action/attestation_status.go b/app/cli/internal/action/attestation_status.go index 55bc5b270..28fd08afa 100644 --- a/app/cli/internal/action/attestation_status.go +++ b/app/cli/internal/action/attestation_status.go @@ -29,6 +29,7 @@ type AttestationStatusOpts struct { *ActionsOpts UseAttestationRemoteState bool isPushed bool + LocalStatePath string } type AttestationStatus struct { @@ -66,7 +67,7 @@ type AttestationStatusResultMaterial struct { } func NewAttestationStatus(cfg *AttestationStatusOpts) (*AttestationStatus, error) { - c, err := newCrafter(cfg.UseAttestationRemoteState, cfg.CPConnection, crafter.WithLogger(&cfg.Logger)) + c, err := newCrafter(&newCrafterStateOpts{enableRemoteState: cfg.UseAttestationRemoteState, localStatePath: cfg.LocalStatePath}, cfg.CPConnection, crafter.WithLogger(&cfg.Logger)) if err != nil { return nil, fmt.Errorf("failed to load crafter: %w", err) }