Skip to content

Commit 69b062d

Browse files
authored
feat(cli): resolve attestation project from repository config (#3517)
Signed-off-by: Vibhav Bobade <vibhav.bobde@gmail.com>
1 parent 18a393d commit 69b062d

2 files changed

Lines changed: 145 additions & 9 deletions

File tree

‎app/cli/cmd/attestation_init.go‎

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,19 @@ func newAttestationInitCmd() *cobra.Command {
5858
return errors.New("workflow name is required, set it via --workflow flag")
5959
}
6060

61-
// Load version from the repository config if not set and not using --latest-version.
62-
if projectVersion == "" && !useLatestVersion {
61+
// Load unresolved project metadata from the repository config.
62+
if projectName == "" || (projectVersion == "" && !useLatestVersion) {
6363
cfg, path, err := repositoryconfig.LoadChainloopYML(".")
6464
if err != nil {
6565
logger.Debug().Msgf("failed to load chainloop config: %s", err)
6666
} else {
67-
logger.Debug().Msgf("loaded version %s from config file %s", cfg.ProjectVersion, path)
68-
projectVersion = cfg.ProjectVersion
67+
logger.Debug().Msgf("loaded project metadata from config file %s", path)
68+
if projectName == "" {
69+
projectName = cfg.ProjectName
70+
}
71+
if projectVersion == "" && !useLatestVersion {
72+
projectVersion = cfg.ProjectVersion
73+
}
6974
}
7075
}
7176

@@ -91,6 +96,10 @@ func newAttestationInitCmd() *cobra.Command {
9196
}
9297
}
9398

99+
if projectName == "" {
100+
return errors.New("project is required, set it via --project or projectName in .chainloop.yml")
101+
}
102+
94103
return nil
95104
},
96105
RunE: func(cmd *cobra.Command, _ []string) error {
@@ -168,10 +177,6 @@ func newAttestationInitCmd() *cobra.Command {
168177
logger.Info().Msg("The attestation is being crafted in dry-run mode. It will not get stored once rendered")
169178
}
170179

171-
if projectName == "" {
172-
logger.Warn().Msg("DEPRECATION WARNING: --project not set, this will be required in the near future")
173-
}
174-
175180
return output.EncodeOutput(flagOutputFormat, res, fullStatusTable)
176181
}}
177182

@@ -189,7 +194,6 @@ func newAttestationInitCmd() *cobra.Command {
189194
cobra.CheckErr(cmd.Flags().MarkDeprecated("workflow-name", "please use --workflow instead"))
190195

191196
cmd.Flags().StringVar(&projectName, "project", "", "name of the project of this workflow")
192-
cobra.CheckErr(cmd.MarkFlagRequired("project"))
193197
cmd.Flags().StringVar(&newWorkflowcontract, "contract", "", "name of an existing contract or the path/URL to a contract file, to attach it to the auto-created workflow (it doesn't update an existing one)")
194198

195199
cmd.Flags().StringVar(&projectVersion, "version", "", "project version, i.e 0.1.0")
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
//
2+
// Copyright 2026 The Chainloop Authors.
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package cmd
17+
18+
import (
19+
"os"
20+
"path/filepath"
21+
"testing"
22+
23+
"github.com/stretchr/testify/assert"
24+
"github.com/stretchr/testify/require"
25+
)
26+
27+
func TestAttestationInitLoadsProjectFromRepositoryConfig(t *testing.T) {
28+
dir := t.TempDir()
29+
require.NoError(t, os.WriteFile(filepath.Join(dir, ".chainloop.yml"), []byte("projectName: web\nprojectVersion: 1.2.3\n"), 0o600))
30+
t.Chdir(dir)
31+
32+
cmd := newAttestationInitCmd()
33+
require.NoError(t, cmd.Flags().Set("workflow", "build"))
34+
require.NoError(t, cmd.PreRunE(cmd, nil))
35+
36+
project, err := cmd.Flags().GetString("project")
37+
require.NoError(t, err)
38+
version, err := cmd.Flags().GetString("version")
39+
require.NoError(t, err)
40+
assert.Equal(t, "web", project)
41+
assert.Equal(t, "1.2.3", version)
42+
}
43+
44+
func TestAttestationInitRepositoryConfigPrecedence(t *testing.T) {
45+
for _, tc := range []struct {
46+
name string
47+
project string
48+
version string
49+
latest bool
50+
config string
51+
wantProject string
52+
wantVersion string
53+
}{
54+
{
55+
name: "project flag wins while version comes from config",
56+
project: "flag-project",
57+
config: "projectName: file-project\nprojectVersion: file-version\n",
58+
wantProject: "flag-project",
59+
wantVersion: "file-version",
60+
},
61+
{
62+
name: "version flag wins while project comes from config",
63+
version: "flag-version",
64+
config: "projectName: file-project\nprojectVersion: file-version\n",
65+
wantProject: "file-project",
66+
wantVersion: "flag-version",
67+
},
68+
{
69+
name: "latest version suppresses config version",
70+
latest: true,
71+
config: "projectName: file-project\nprojectVersion: file-version\n",
72+
wantProject: "file-project",
73+
},
74+
{
75+
name: "resolved flags do not read malformed config",
76+
project: "flag-project",
77+
version: "flag-version",
78+
config: ":\ninvalid: [yaml\n",
79+
wantProject: "flag-project",
80+
wantVersion: "flag-version",
81+
},
82+
} {
83+
t.Run(tc.name, func(t *testing.T) {
84+
dir := t.TempDir()
85+
require.NoError(t, os.WriteFile(filepath.Join(dir, ".chainloop.yml"), []byte(tc.config), 0o600))
86+
t.Chdir(dir)
87+
88+
cmd := newAttestationInitCmd()
89+
require.NoError(t, cmd.Flags().Set("workflow", "build"))
90+
if tc.project != "" {
91+
require.NoError(t, cmd.Flags().Set("project", tc.project))
92+
}
93+
if tc.version != "" {
94+
require.NoError(t, cmd.Flags().Set("version", tc.version))
95+
}
96+
if tc.latest {
97+
require.NoError(t, cmd.Flags().Set("latest-version", "true"))
98+
}
99+
100+
require.NoError(t, cmd.PreRunE(cmd, nil))
101+
project, err := cmd.Flags().GetString("project")
102+
require.NoError(t, err)
103+
version, err := cmd.Flags().GetString("version")
104+
require.NoError(t, err)
105+
assert.Equal(t, tc.wantProject, project)
106+
assert.Equal(t, tc.wantVersion, version)
107+
})
108+
}
109+
}
110+
111+
func TestAttestationInitRequiresProjectFromFlagOrRepositoryConfig(t *testing.T) {
112+
for _, tc := range []struct {
113+
name string
114+
config string
115+
}{
116+
{name: "missing config"},
117+
{name: "config without project", config: "projectVersion: 1.2.3\n"},
118+
} {
119+
t.Run(tc.name, func(t *testing.T) {
120+
dir := t.TempDir()
121+
if tc.config != "" {
122+
require.NoError(t, os.WriteFile(filepath.Join(dir, ".chainloop.yml"), []byte(tc.config), 0o600))
123+
}
124+
t.Chdir(dir)
125+
126+
cmd := newAttestationInitCmd()
127+
require.NoError(t, cmd.Flags().Set("workflow", "build"))
128+
129+
assert.EqualError(t, cmd.PreRunE(cmd, nil), "project is required, set it via --project or projectName in .chainloop.yml")
130+
})
131+
}
132+
}

0 commit comments

Comments
 (0)