diff --git a/.gitignore b/.gitignore
index 1de1c8e7b418..e315bbe907c4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -55,3 +55,4 @@ install_manifest.txt
*.cbp
!/.copr/Makefile
!/.buildkite/Makefile
+.vscode/launch.json
diff --git a/client/go/internal/cli/cmd/cert.go b/client/go/internal/cli/cmd/cert.go
index 9e0b8f6805cc..dcf0ad6a7f5f 100644
--- a/client/go/internal/cli/cmd/cert.go
+++ b/client/go/internal/cli/cmd/cert.go
@@ -157,7 +157,8 @@ func doCertAdd(cli *CLI, overwriteCertificate bool, args []string) error {
if err != nil {
return err
}
- if pkg.HasCertificate() && !overwriteCertificate {
+ defaultCertPath := []string{"security/clients.pem"}
+ if pkg.HasCertificate(defaultCertPath) && !overwriteCertificate {
return errHint(fmt.Errorf("application package '%s' already contains a certificate", pkg.Path), "Use -f flag to force overwriting")
}
return requireCertificate(true, false, cli, target, pkg)
@@ -182,7 +183,11 @@ func requireCertificate(force, ignoreZip bool, cli *CLI, target vespa.Target, pk
if force {
return copyCertificate(tlsOptions, cli, pkg)
}
- if pkg.HasCertificate() {
+ servicesXML, err := readServicesXML(pkg)
+ if err != nil {
+ return fmt.Errorf("Error reading services.xml: %w", err)
+ }
+ if pkg.HasCertificate(servicesXML.CertPaths()) {
if cli.isCI() {
return nil // A matching certificate is not required in CI environments
}
diff --git a/client/go/internal/cli/cmd/deploy.go b/client/go/internal/cli/cmd/deploy.go
index 19c48f942a66..dc6a511e3370 100644
--- a/client/go/internal/cli/cmd/deploy.go
+++ b/client/go/internal/cli/cmd/deploy.go
@@ -81,8 +81,15 @@ $ vespa deploy -t cloud -z perf.aws-us-east-1c`,
return err
}
var result vespa.PrepareResult
+ var certPaths []string
+ servicesXML, err := readServicesXML(pkg)
+ if err != nil {
+ certPaths = []string{}
+ } else {
+ certPaths = servicesXML.CertPaths()
+ }
err = cli.spinner(cli.Stderr, "Uploading application package...", func() error {
- result, err = vespa.Deploy(opts)
+ result, err = vespa.Deploy(opts, certPaths)
return err
})
if err != nil {
diff --git a/client/go/internal/cli/cmd/prod.go b/client/go/internal/cli/cmd/prod.go
index 139e4690ed2e..b4476ba3b295 100644
--- a/client/go/internal/cli/cmd/prod.go
+++ b/client/go/internal/cli/cmd/prod.go
@@ -165,7 +165,12 @@ $ vespa prod deploy`,
AuthorEmail: options.authorEmail,
SourceURL: options.sourceURL,
}
- build, err := vespa.Submit(deployment, submission)
+
+ servicesXML, err := readServicesXML(pkg)
+ if err != nil {
+ return fmt.Errorf("Error reading services.xml: %w", err)
+ }
+ build, err := vespa.Submit(deployment, submission, servicesXML.CertPaths())
if err != nil {
return fmt.Errorf("could not deploy application: %w", err)
} else {
diff --git a/client/go/internal/cli/cmd/prod_test.go b/client/go/internal/cli/cmd/prod_test.go
index 8ea20b3bbe50..c1d73c7c13e0 100644
--- a/client/go/internal/cli/cmd/prod_test.go
+++ b/client/go/internal/cli/cmd/prod_test.go
@@ -61,6 +61,11 @@ func TestProdInit(t *testing.T) {
containerFragment := `
+
+
+
+
+
`
assert.Contains(t, servicesXML, containerFragment)
@@ -111,6 +116,11 @@ func createApplication(t *testing.T, pkgDir string, java bool, skipTests bool) {
+
+
+
+
+
diff --git a/client/go/internal/vespa/application.go b/client/go/internal/vespa/application.go
index d499006c9820..973505218316 100644
--- a/client/go/internal/vespa/application.go
+++ b/client/go/internal/vespa/application.go
@@ -20,7 +20,22 @@ type ApplicationPackage struct {
TestPath string
}
-func (ap *ApplicationPackage) HasCertificate() bool { return ap.hasFile("security", "clients.pem") }
+func (ap *ApplicationPackage) HasCertificate(certPaths []string) bool {
+ if len(certPaths) == 0 {
+ if ap.hasFile("security", "clients.pem") {
+ return true
+ } else {
+ return false
+ }
+ } else {
+ for _, certPath := range certPaths {
+ if ap.hasFile(certPath) {
+ return true
+ }
+ }
+ return false
+ }
+}
func (ap *ApplicationPackage) HasMatchingCertificate(certificatePEM []byte) (bool, error) {
clientsPEM, err := os.ReadFile(filepath.Join(ap.Path, "security", "clients.pem"))
diff --git a/client/go/internal/vespa/deploy.go b/client/go/internal/vespa/deploy.go
index 2c96b8b09352..84e68f24c19a 100644
--- a/client/go/internal/vespa/deploy.go
+++ b/client/go/internal/vespa/deploy.go
@@ -334,13 +334,13 @@ func Deactivate(deployment DeploymentOptions) error {
}
// Deploy deploys an application.
-func Deploy(deployment DeploymentOptions) (PrepareResult, error) {
+func Deploy(deployment DeploymentOptions, certPaths []string) (PrepareResult, error) {
var (
u *url.URL
err error
)
if deployment.Target.IsCloud() {
- if err := checkDeploymentOpts(deployment); err != nil {
+ if err := checkDeploymentOpts(deployment, certPaths); err != nil {
return PrepareResult{}, err
}
if deployment.Target.Deployment().Zone.Environment == "" || deployment.Target.Deployment().Zone.Region == "" {
@@ -373,11 +373,11 @@ func copyToPart(dst *multipart.Writer, src io.Reader, fieldname, filename string
return nil
}
-func Submit(opts DeploymentOptions, submission Submission) (int64, error) {
+func Submit(opts DeploymentOptions, submission Submission, certPaths []string) (int64, error) {
if !opts.Target.IsCloud() {
return 0, fmt.Errorf("%s: deploy is unsupported by %s target", opts, opts.Target.Type())
}
- if err := checkDeploymentOpts(opts); err != nil {
+ if err := checkDeploymentOpts(opts, certPaths); err != nil {
return 0, err
}
submitURL := opts.Target.Deployment().System.SubmitURL(opts.Target.Deployment())
@@ -449,8 +449,8 @@ func deployServiceDo(request *http.Request, timeout time.Duration, opts Deployme
return s.Do(request, timeout)
}
-func checkDeploymentOpts(opts DeploymentOptions) error {
- if opts.Target.Type() == TargetCloud && !opts.ApplicationPackage.HasCertificate() {
+func checkDeploymentOpts(opts DeploymentOptions, certPaths []string) error {
+ if opts.Target.Type() == TargetCloud && !opts.ApplicationPackage.HasCertificate(certPaths) {
return fmt.Errorf("%s: missing certificate in package", opts)
}
if !opts.Target.IsCloud() && !opts.Version.IsZero() {
diff --git a/client/go/internal/vespa/deploy_test.go b/client/go/internal/vespa/deploy_test.go
index 4c2fb9122246..3a5c14fbfa0e 100644
--- a/client/go/internal/vespa/deploy_test.go
+++ b/client/go/internal/vespa/deploy_test.go
@@ -27,7 +27,7 @@ func TestDeploy(t *testing.T) {
Target: target,
ApplicationPackage: ApplicationPackage{Path: appDir},
}
- _, err := Deploy(opts)
+ _, err := Deploy(opts, []string{})
assert.Nil(t, err)
assert.Equal(t, 1, len(httpClient.Requests))
req := httpClient.LastRequest
@@ -49,7 +49,7 @@ func TestDeployCloud(t *testing.T) {
Target: target,
ApplicationPackage: ApplicationPackage{Path: appDir},
}
- _, err := Deploy(opts)
+ _, err := Deploy(opts, []string{})
require.Nil(t, err)
assert.Equal(t, 1, len(httpClient.Requests))
req := httpClient.LastRequest
@@ -62,7 +62,7 @@ func TestDeployCloud(t *testing.T) {
assert.False(t, hasDeployOptions)
opts.Version = version.MustParse("1.2.3")
- _, err = Deploy(opts)
+ _, err = Deploy(opts, []string{})
require.Nil(t, err)
req = httpClient.LastRequest
values = parseMultiPart(t, req)
@@ -83,7 +83,7 @@ func TestSubmit(t *testing.T) {
ApplicationPackage: ApplicationPackage{Path: appDir},
}
httpClient.NextResponseString(200, `{"build": 42}`)
- build, err := Submit(opts, Submission{})
+ build, err := Submit(opts, Submission{}, []string{})
require.Nil(t, err)
require.Equal(t, int64(42), build)
require.Nil(t, httpClient.LastRequest.ParseMultipartForm(1<<20))
@@ -102,7 +102,7 @@ func TestSubmit(t *testing.T) {
Description: "broken garbage",
AuthorEmail: "foo@example.com",
SourceURL: "https://github.com/foo/repo",
- })
+ }, []string{})
require.Nil(t, err)
require.Equal(t, int64(43), build)
require.Nil(t, httpClient.LastRequest.ParseMultipartForm(1<<20))
diff --git a/client/go/internal/vespa/xml/config.go b/client/go/internal/vespa/xml/config.go
index d1c16b016523..47e102867cf7 100644
--- a/client/go/internal/vespa/xml/config.go
+++ b/client/go/internal/vespa/xml/config.go
@@ -75,9 +75,10 @@ type Services struct {
}
type Container struct {
- Root xml.Name `xml:"container"`
- ID string `xml:"id,attr"`
- Nodes Nodes `xml:"nodes"`
+ Root xml.Name `xml:"container"`
+ ID string `xml:"id,attr"`
+ Nodes Nodes `xml:"nodes"`
+ Clients Clients `xml:"clients"`
}
type Content struct {
@@ -85,6 +86,19 @@ type Content struct {
Nodes Nodes `xml:"nodes"`
}
+type Clients struct {
+ Client []Client `xml:"client"`
+}
+
+type Client struct {
+ Id string `xml:"id,attr"`
+ Certificate Certificate `xml:"certificate"`
+}
+
+type Certificate struct {
+ File string `xml:"file,attr"`
+}
+
type Nodes struct {
Count string `xml:"count,attr"`
Resources *Resources `xml:"resources,omitempty"`
@@ -98,6 +112,17 @@ type Resources struct {
func (s Services) String() string { return s.rawXML.String() }
+// Reads file paths from services.xml
+func (s Services) CertPaths() []string {
+ var certificates []string
+ for _, container := range s.Container {
+ for _, client := range container.Clients.Client {
+ certificates = append(certificates, client.Certificate.File)
+ }
+ }
+ return certificates
+}
+
// Replace replaces any elements of name found under parentName with data.
func (s *Services) Replace(parentName, name string, data interface{}) error {
rewritten, err := Replace(&s.rawXML, parentName, name, data)
diff --git a/client/go/internal/vespa/xml/config_test.go b/client/go/internal/vespa/xml/config_test.go
index e17a608b5545..f4a9064f26a3 100644
--- a/client/go/internal/vespa/xml/config_test.go
+++ b/client/go/internal/vespa/xml/config_test.go
@@ -243,6 +243,51 @@ func TestReadServicesNoResources(t *testing.T) {
}
}
+func TestReadClientPaths(t *testing.T) {
+ s := `
+
+
+
+
+
+
+
+
+
+
+
+
+`
+ services, err := ReadServices(strings.NewReader(s))
+ if err != nil {
+ t.Fatal(err)
+ }
+ certPaths := services.CertPaths()
+ if got := certPaths[0]; got != "security/test.pem" {
+ t.Errorf("got %+v, want security/test.pem", got)
+ }
+ if got := certPaths[1]; got != "security/other.pem" {
+ t.Errorf("got %+v, want security/other.pem", got)
+ }
+}
+
+func TestReadNoClientPaths(t *testing.T) {
+ s := `
+
+
+
+
+`
+ services, err := ReadServices(strings.NewReader(s))
+ if err != nil {
+ t.Fatal(err)
+ }
+ certPaths := services.CertPaths()
+ if got := certPaths; got != nil {
+ t.Errorf("got %+v, want nil", got)
+ }
+}
+
func TestParseResources(t *testing.T) {
assertResources(t, "foo", Resources{}, true)
assertResources(t, "vcpu=2,memory=4Gb", Resources{}, true)