diff --git a/api/internal/genapi/main.go b/api/internal/genapi/main.go index 62a9b7eaee..028b7322c4 100644 --- a/api/internal/genapi/main.go +++ b/api/internal/genapi/main.go @@ -5,4 +5,5 @@ func main() { writeStructTemplates() writeCreateFuncs() writeReadFuncs() + writeUpdateFuncs() } diff --git a/api/internal/genapi/templates.go b/api/internal/genapi/templates.go index f1e1b5aec8..c3a8f4d302 100644 --- a/api/internal/genapi/templates.go +++ b/api/internal/genapi/templates.go @@ -58,6 +58,7 @@ func writeStructTemplates() { } } +// TODO: Add documentation around SetDefault and how it behaves when the field corresponding to the provided key is already set. var structTemplate = template.Must(template.New("").Parse( `// Code generated by "make api"; DO NOT EDIT. package {{ .Package }} diff --git a/api/internal/genapi/update.go b/api/internal/genapi/update.go new file mode 100644 index 0000000000..3688fabd2b --- /dev/null +++ b/api/internal/genapi/update.go @@ -0,0 +1,112 @@ +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "os" + "strings" + "text/template" +) + +type updateInfo struct { + baseType string + targetType string + path string +} + +var updateFuncs = map[string][]*updateInfo{ + "scopes": { + { + "Organization", + "Project", + "projects/%s", + }, + }, +} + +func writeUpdateFuncs() { + for outPkg, funcs := range updateFuncs { + outFile := os.Getenv("GEN_BASEPATH") + fmt.Sprintf("/api/%s/update.gen.go", outPkg) + outBuf := bytes.NewBuffer([]byte(fmt.Sprintf( + `// Code generated by "make api"; DO NOT EDIT. +package %s +`, outPkg))) + for _, updateInfo := range funcs { + updateFuncTemplate.Execute(outBuf, struct { + BaseType string + TargetType string + LowerTargetType string + Path string + }{ + BaseType: updateInfo.baseType, + TargetType: updateInfo.targetType, + LowerTargetType: strings.ToLower(updateInfo.targetType), + Path: updateInfo.path, + }) + } + if err := ioutil.WriteFile(outFile, outBuf.Bytes(), 0644); err != nil { + fmt.Printf("error writing file %q: %v\n", outFile, err) + os.Exit(1) + } + } +} + +var updateFuncTemplate = template.Must(template.New("").Parse( + ` +func (s {{ .BaseType }}) Update{{ .TargetType }}(ctx context.Context, {{ .LowerTargetType }} *{{ .TargetType }}) (*{{ .TargetType }}, *api.Error, error) { + if s.Client == nil { + return nil, nil, fmt.Errorf("nil client in Create{{ .TargetType }} request") + } + if s.Id == "" { + {{ if (eq .BaseType "Organization") }} + // Assume the client has been configured with organization already and + // move on + {{ else if (eq .BaseType "Project") }} + // Assume the client has been configured with project already and move + // on + {{ else }} + return nil, nil, fmt.Errorf("missing {{ .BaseType}} ID in Create{{ .TargetType }} request") + {{ end }} + } else { + // If it's explicitly set here, override anything that might be in the + // client + {{ if (eq .BaseType "Organization") }} + ctx = context.WithValue(ctx, "org", s.Id) + {{ else if (eq .BaseType "Project") }} + ctx = context.WithValue(ctx, "project", s.Id) + {{ end }} + } + + id := {{ .LowerTargetType }}.Id + {{ .LowerTargetType }}.Id = "" + + req, err := s.Client.NewRequest(ctx, "PATCH", fmt.Sprintf("{{ .Path }}", id), {{ .LowerTargetType }}) + if err != nil { + return nil, nil, fmt.Errorf("error creating Create{{ .TargetType }} request: %w", err) + } + + resp, err := s.Client.Do(req) + if err != nil { + return nil, nil, fmt.Errorf("error performing client request during Update{{ .TargetType }} call: %w", err) + } + + target := new({{ .TargetType }}) + apiErr, err := resp.Decode(target) + if err != nil { + return nil, nil, fmt.Errorf("error decoding Update{{ .TargetType }} repsonse: %w", err) + } + + {{ if (eq .TargetType "Organization") }} + target.Client = s.Client.Clone() + target.Client.SetOrgnization(target.Id) + {{ else if (eq .TargetType "Project") }} + target.Client = s.Client.Clone() + target.Client.SetProject(target.Id) + {{ else }} + target.Client = s.Client + {{ end }} + + return target, apiErr, nil +} +`)) diff --git a/api/scopes/project_test.go b/api/scopes/project_test.go index 4c57443215..31fd405860 100644 --- a/api/scopes/project_test.go +++ b/api/scopes/project_test.go @@ -17,24 +17,26 @@ func TestProjects_Crud(t *testing.T) { Client: client, } - name := "foo" - - checkProject := func(step string, p *Project, apiErr *api.Error, err error) { + checkProject := func(step string, p *Project, apiErr *api.Error, err error, wantedName string) { assert := assert.New(t) assert.NoError(err, step) assert.Nil(apiErr, step) assert.NotNil(p, "returned project", step) - assert.NotNil(p, "returned project name", step) - assert.Equal(name, *p.Name, step) + assert.Equal(wantedName, *p.Name, step) } - p, apiErr, err := org.CreateProject(tc.Context(), &Project{Name: api.String(name)}) - checkProject("create", p, apiErr, err) + p, apiErr, err := org.CreateProject(tc.Context(), &Project{Name: api.String("foo")}) + checkProject("create", p, apiErr, err, "foo") p, apiErr, err = org.ReadProject(tc.Context(), &Project{Id: p.Id}) - checkProject("read", p, apiErr, err) + checkProject("read", p, apiErr, err, "foo") + + p = &Project{Id: p.Id} + p.Name = api.String("bar") + p, apiErr, err = org.UpdateProject(tc.Context(), p) + checkProject("update", p, apiErr, err, "bar") - // TODO: Update and Delete + // TODO: Delete // TODO: Error conditions once the proper errors are being returned. // Probably as parallel subtests against the same DB. diff --git a/api/scopes/update.gen.go b/api/scopes/update.gen.go new file mode 100644 index 0000000000..a003819e39 --- /dev/null +++ b/api/scopes/update.gen.go @@ -0,0 +1,51 @@ +// Code generated by "make api"; DO NOT EDIT. +package scopes + +import ( + "context" + "fmt" + + "github.com/hashicorp/watchtower/api" +) + +func (s Organization) UpdateProject(ctx context.Context, project *Project) (*Project, *api.Error, error) { + if s.Client == nil { + return nil, nil, fmt.Errorf("nil client in CreateProject request") + } + if s.Id == "" { + + // Assume the client has been configured with organization already and + // move on + + } else { + // If it's explicitly set here, override anything that might be in the + // client + + ctx = context.WithValue(ctx, "org", s.Id) + + } + + id := project.Id + project.Id = "" + + req, err := s.Client.NewRequest(ctx, "PATCH", fmt.Sprintf("projects/%s", id), project) + if err != nil { + return nil, nil, fmt.Errorf("error creating CreateProject request: %w", err) + } + + resp, err := s.Client.Do(req) + if err != nil { + return nil, nil, fmt.Errorf("error performing client request during UpdateProject call: %w", err) + } + + target := new(Project) + apiErr, err := resp.Decode(target) + if err != nil { + return nil, nil, fmt.Errorf("error decoding UpdateProject repsonse: %w", err) + } + + target.Client = s.Client.Clone() + target.Client.SetProject(target.Id) + + return target, apiErr, nil +} diff --git a/go.mod b/go.mod index d0ccef116d..bf2ac75fc4 100644 --- a/go.mod +++ b/go.mod @@ -51,6 +51,7 @@ require ( go.mongodb.org/mongo-driver v1.3.2 // indirect golang.org/x/net v0.0.0-20200506145744-7e3656a0809f golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1 + golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380 google.golang.org/grpc v1.29.1 google.golang.org/protobuf v1.22.0 diff --git a/go.sum b/go.sum index b38d01d9f4..795c858d40 100644 --- a/go.sum +++ b/go.sum @@ -568,8 +568,6 @@ github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyN github.com/hashicorp/consul/sdk v0.2.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-alpnmux v0.0.0-20200323180452-dee08f00df54 h1:WhMHPQosFuXFjt2wpRzX2eqR1WpnS+35MFzmG3trg3Y= -github.com/hashicorp/go-alpnmux v0.0.0-20200323180452-dee08f00df54/go.mod h1:KvpteZzIafT4tRAuQ9vVRBgZyqeVCS2B2177fNAyEZc= github.com/hashicorp/go-alpnmux v0.0.0-20200513011953-0293f5d23c31 h1:pxqI71/0R1WIASjQEJ9W9skCKYiREEkRoXFvHCZH1pg= github.com/hashicorp/go-alpnmux v0.0.0-20200513011953-0293f5d23c31/go.mod h1:KvpteZzIafT4tRAuQ9vVRBgZyqeVCS2B2177fNAyEZc= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= diff --git a/internal/db/db_test/db_test.pb.go b/internal/db/db_test/db_test.pb.go index a8fa3376ec..b8f5020b82 100644 --- a/internal/db/db_test/db_test.pb.go +++ b/internal/db/db_test/db_test.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.21.0 +// protoc-gen-go v1.22.0 // protoc v3.11.4 // source: controller/storage/db/db_test/v1/db_test.proto diff --git a/internal/gen/controller/api/error.pb.go b/internal/gen/controller/api/error.pb.go index 3649fca6b9..72d4908dd4 100644 --- a/internal/gen/controller/api/error.pb.go +++ b/internal/gen/controller/api/error.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.21.0 +// protoc-gen-go v1.22.0 // protoc v3.11.4 // source: controller/api/v1/error.proto diff --git a/internal/gen/controller/api/resources/hosts/host.pb.go b/internal/gen/controller/api/resources/hosts/host.pb.go index 3f3a2c4465..4f4f8a937c 100644 --- a/internal/gen/controller/api/resources/hosts/host.pb.go +++ b/internal/gen/controller/api/resources/hosts/host.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.21.0 +// protoc-gen-go v1.22.0 // protoc v3.11.4 // source: controller/api/resources/hosts/v1/host.proto diff --git a/internal/gen/controller/api/resources/hosts/host_catalog.pb.go b/internal/gen/controller/api/resources/hosts/host_catalog.pb.go index 44e52678fc..b857ddd921 100644 --- a/internal/gen/controller/api/resources/hosts/host_catalog.pb.go +++ b/internal/gen/controller/api/resources/hosts/host_catalog.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.21.0 +// protoc-gen-go v1.22.0 // protoc v3.11.4 // source: controller/api/resources/hosts/v1/host_catalog.proto diff --git a/internal/gen/controller/api/resources/hosts/host_set.pb.go b/internal/gen/controller/api/resources/hosts/host_set.pb.go index 1bcce6cde0..8c56f14aac 100644 --- a/internal/gen/controller/api/resources/hosts/host_set.pb.go +++ b/internal/gen/controller/api/resources/hosts/host_set.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.21.0 +// protoc-gen-go v1.22.0 // protoc v3.11.4 // source: controller/api/resources/hosts/v1/host_set.proto diff --git a/internal/gen/controller/api/resources/scopes/organization.pb.go b/internal/gen/controller/api/resources/scopes/organization.pb.go index 1fd6089fcb..6b73f72dd4 100644 --- a/internal/gen/controller/api/resources/scopes/organization.pb.go +++ b/internal/gen/controller/api/resources/scopes/organization.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.21.0 +// protoc-gen-go v1.22.0 // protoc v3.11.4 // source: controller/api/resources/scopes/v1/organization.proto diff --git a/internal/gen/controller/api/resources/scopes/project.pb.go b/internal/gen/controller/api/resources/scopes/project.pb.go index 33972f1562..2705aeef4d 100644 --- a/internal/gen/controller/api/resources/scopes/project.pb.go +++ b/internal/gen/controller/api/resources/scopes/project.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.21.0 +// protoc-gen-go v1.22.0 // protoc v3.11.4 // source: controller/api/resources/scopes/v1/project.proto diff --git a/internal/gen/controller/api/services/host_catalog_service.pb.go b/internal/gen/controller/api/services/host_catalog_service.pb.go index 0008233329..8715a90ce0 100644 --- a/internal/gen/controller/api/services/host_catalog_service.pb.go +++ b/internal/gen/controller/api/services/host_catalog_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.21.0 +// protoc-gen-go v1.22.0 // protoc v3.11.4 // source: controller/api/services/v1/host_catalog_service.proto diff --git a/internal/gen/controller/api/services/host_catalog_service.pb.gw.go b/internal/gen/controller/api/services/host_catalog_service.pb.gw.go index 5d5a37713d..1e7a23c750 100644 --- a/internal/gen/controller/api/services/host_catalog_service.pb.gw.go +++ b/internal/gen/controller/api/services/host_catalog_service.pb.gw.go @@ -135,7 +135,10 @@ func local_request_HostCatalogService_GetHostCatalog_0(ctx context.Context, mars return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostCatalogService_GetHostCatalog_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostCatalogService_GetHostCatalog_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -226,7 +229,10 @@ func local_request_HostCatalogService_GetHostCatalog_1(ctx context.Context, mars return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostCatalogService_GetHostCatalog_1); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostCatalogService_GetHostCatalog_1); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -317,7 +323,10 @@ func local_request_HostCatalogService_ListHostCatalogs_0(ctx context.Context, ma return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "project_id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostCatalogService_ListHostCatalogs_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostCatalogService_ListHostCatalogs_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -386,7 +395,10 @@ func local_request_HostCatalogService_ListHostCatalogs_1(ctx context.Context, ma return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "org_id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostCatalogService_ListHostCatalogs_1); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostCatalogService_ListHostCatalogs_1); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -563,7 +575,10 @@ func local_request_HostCatalogService_CreateHostCatalog_1(ctx context.Context, m return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "org_id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostCatalogService_CreateHostCatalog_1); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostCatalogService_CreateHostCatalog_1); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -708,7 +723,10 @@ func local_request_HostCatalogService_UpdateHostCatalog_0(ctx context.Context, m return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostCatalogService_UpdateHostCatalog_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostCatalogService_UpdateHostCatalog_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -831,7 +849,10 @@ func local_request_HostCatalogService_UpdateHostCatalog_1(ctx context.Context, m return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostCatalogService_UpdateHostCatalog_1); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostCatalogService_UpdateHostCatalog_1); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -1020,7 +1041,10 @@ func local_request_HostCatalogService_DeleteHostCatalog_1(ctx context.Context, m return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostCatalogService_DeleteHostCatalog_1); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostCatalogService_DeleteHostCatalog_1); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } diff --git a/internal/gen/controller/api/services/host_service.pb.go b/internal/gen/controller/api/services/host_service.pb.go index dad742ec8f..21ec9967a4 100644 --- a/internal/gen/controller/api/services/host_service.pb.go +++ b/internal/gen/controller/api/services/host_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.21.0 +// protoc-gen-go v1.22.0 // protoc v3.11.4 // source: controller/api/services/v1/host_service.proto diff --git a/internal/gen/controller/api/services/host_service.pb.gw.go b/internal/gen/controller/api/services/host_service.pb.gw.go index 08236f75b5..1e46bcbd9c 100644 --- a/internal/gen/controller/api/services/host_service.pb.gw.go +++ b/internal/gen/controller/api/services/host_service.pb.gw.go @@ -157,7 +157,10 @@ func local_request_HostService_GetHost_0(ctx context.Context, marshaler runtime. return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostService_GetHost_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostService_GetHost_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -270,7 +273,10 @@ func local_request_HostService_GetHost_1(ctx context.Context, marshaler runtime. return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostService_GetHost_1); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostService_GetHost_1); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -383,7 +389,10 @@ func local_request_HostService_ListHosts_0(ctx context.Context, marshaler runtim return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "host_catalog_id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostService_ListHosts_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostService_ListHosts_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -452,7 +461,10 @@ func local_request_HostService_ListHosts_1(ctx context.Context, marshaler runtim return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "org_id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostService_ListHosts_1); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostService_ListHosts_1); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -673,7 +685,10 @@ func local_request_HostService_CreateHost_1(ctx context.Context, marshaler runti return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "host_catalog_id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostService_CreateHost_1); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostService_CreateHost_1); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -840,7 +855,10 @@ func local_request_HostService_UpdateHost_0(ctx context.Context, marshaler runti return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostService_UpdateHost_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostService_UpdateHost_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -985,7 +1003,10 @@ func local_request_HostService_UpdateHost_1(ctx context.Context, marshaler runti return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostService_UpdateHost_1); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostService_UpdateHost_1); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -1218,7 +1239,10 @@ func local_request_HostService_DeleteHost_1(ctx context.Context, marshaler runti return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostService_DeleteHost_1); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostService_DeleteHost_1); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } diff --git a/internal/gen/controller/api/services/host_set_service.pb.go b/internal/gen/controller/api/services/host_set_service.pb.go index bbe223268a..f10113da57 100644 --- a/internal/gen/controller/api/services/host_set_service.pb.go +++ b/internal/gen/controller/api/services/host_set_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.21.0 +// protoc-gen-go v1.22.0 // protoc v3.11.4 // source: controller/api/services/v1/host_set_service.proto diff --git a/internal/gen/controller/api/services/host_set_service.pb.gw.go b/internal/gen/controller/api/services/host_set_service.pb.gw.go index fd740a351a..79bea9e453 100644 --- a/internal/gen/controller/api/services/host_set_service.pb.gw.go +++ b/internal/gen/controller/api/services/host_set_service.pb.gw.go @@ -157,7 +157,10 @@ func local_request_HostSetService_GetHostSet_0(ctx context.Context, marshaler ru return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostSetService_GetHostSet_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostSetService_GetHostSet_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -270,7 +273,10 @@ func local_request_HostSetService_GetHostSet_1(ctx context.Context, marshaler ru return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostSetService_GetHostSet_1); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostSetService_GetHostSet_1); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -383,7 +389,10 @@ func local_request_HostSetService_ListHostSets_0(ctx context.Context, marshaler return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "host_catalog_id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostSetService_ListHostSets_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostSetService_ListHostSets_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -474,7 +483,10 @@ func local_request_HostSetService_ListHostSets_1(ctx context.Context, marshaler return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "host_catalog_id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostSetService_ListHostSets_1); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostSetService_ListHostSets_1); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -695,7 +707,10 @@ func local_request_HostSetService_CreateHostSet_1(ctx context.Context, marshaler return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "host_catalog_id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostSetService_CreateHostSet_1); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostSetService_CreateHostSet_1); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -862,7 +877,10 @@ func local_request_HostSetService_UpdateHostSet_0(ctx context.Context, marshaler return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostSetService_UpdateHostSet_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostSetService_UpdateHostSet_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -1007,7 +1025,10 @@ func local_request_HostSetService_UpdateHostSet_1(ctx context.Context, marshaler return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostSetService_UpdateHostSet_1); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostSetService_UpdateHostSet_1); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -1240,7 +1261,10 @@ func local_request_HostSetService_DeleteHostSet_1(ctx context.Context, marshaler return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostSetService_DeleteHostSet_1); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostSetService_DeleteHostSet_1); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -1375,7 +1399,10 @@ func local_request_HostSetService_AddToHostSet_0(ctx context.Context, marshaler return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostSetService_AddToHostSet_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostSetService_AddToHostSet_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -1488,7 +1515,10 @@ func local_request_HostSetService_AddToHostSet_1(ctx context.Context, marshaler return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostSetService_AddToHostSet_1); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostSetService_AddToHostSet_1); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -1623,7 +1653,10 @@ func local_request_HostSetService_RemoveFromHostSet_0(ctx context.Context, marsh return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostSetService_RemoveFromHostSet_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostSetService_RemoveFromHostSet_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -1736,7 +1769,10 @@ func local_request_HostSetService_RemoveFromHostSet_1(ctx context.Context, marsh return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_HostSetService_RemoveFromHostSet_1); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_HostSetService_RemoveFromHostSet_1); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } diff --git a/internal/gen/controller/api/services/project_service.pb.go b/internal/gen/controller/api/services/project_service.pb.go index 39dfadf469..7fe53f8dc7 100644 --- a/internal/gen/controller/api/services/project_service.pb.go +++ b/internal/gen/controller/api/services/project_service.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.21.0 +// protoc-gen-go v1.22.0 // protoc v3.11.4 // source: controller/api/services/v1/project_service.proto diff --git a/internal/gen/controller/api/services/project_service.pb.gw.go b/internal/gen/controller/api/services/project_service.pb.gw.go index 2928952c13..80e7a5e18b 100644 --- a/internal/gen/controller/api/services/project_service.pb.gw.go +++ b/internal/gen/controller/api/services/project_service.pb.gw.go @@ -113,7 +113,10 @@ func local_request_ProjectService_GetProject_0(ctx context.Context, marshaler ru return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_ProjectService_GetProject_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ProjectService_GetProject_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -182,7 +185,10 @@ func local_request_ProjectService_ListProjects_0(ctx context.Context, marshaler return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "org_id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_ProjectService_ListProjects_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ProjectService_ListProjects_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } @@ -375,7 +381,10 @@ func local_request_ProjectService_UpdateProject_0(ctx context.Context, marshaler return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "id", err) } - if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_ProjectService_UpdateProject_0); err != nil { + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ProjectService_UpdateProject_0); err != nil { return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) } diff --git a/internal/gen/controller/storage/placeholder.pb.go b/internal/gen/controller/storage/placeholder.pb.go index 8231b453cb..dbc42b7619 100644 --- a/internal/gen/controller/storage/placeholder.pb.go +++ b/internal/gen/controller/storage/placeholder.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.21.0 +// protoc-gen-go v1.22.0 // protoc v3.11.4 // source: controller/storage/v1/placeholder.proto diff --git a/internal/iam/store/scope.pb.go b/internal/iam/store/scope.pb.go index 497ac63597..3b63ce185b 100644 --- a/internal/iam/store/scope.pb.go +++ b/internal/iam/store/scope.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.21.0 +// protoc-gen-go v1.22.0 // protoc v3.11.4 // source: controller/storage/iam/store/v1/scope.proto diff --git a/internal/iam/store/timestamp.pb.go b/internal/iam/store/timestamp.pb.go index c2460a6127..3a4aa39147 100644 --- a/internal/iam/store/timestamp.pb.go +++ b/internal/iam/store/timestamp.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.21.0 +// protoc-gen-go v1.22.0 // protoc v3.11.4 // source: controller/storage/iam/store/v1/timestamp.proto diff --git a/internal/oplog/any_operation.pb.go b/internal/oplog/any_operation.pb.go index e281b697f9..55c57f2bbe 100644 --- a/internal/oplog/any_operation.pb.go +++ b/internal/oplog/any_operation.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.21.0 +// protoc-gen-go v1.22.0 // protoc v3.11.4 // source: controller/storage/oplog/v1/any_operation.proto diff --git a/internal/oplog/oplog_test/oplog_test.pb.go b/internal/oplog/oplog_test/oplog_test.pb.go index 70d146d22c..3253b23ada 100644 --- a/internal/oplog/oplog_test/oplog_test.pb.go +++ b/internal/oplog/oplog_test/oplog_test.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.21.0 +// protoc-gen-go v1.22.0 // protoc v3.11.4 // source: controller/storage/oplog/test/v1/oplog_test.proto diff --git a/internal/oplog/store/oplog.pb.go b/internal/oplog/store/oplog.pb.go index 3587ba86a2..4caac991fc 100644 --- a/internal/oplog/store/oplog.pb.go +++ b/internal/oplog/store/oplog.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.21.0 +// protoc-gen-go v1.22.0 // protoc v3.11.4 // source: controller/storage/oplog/store/v1/oplog.proto diff --git a/internal/servers/controller/handlers/projects/project_service.go b/internal/servers/controller/handlers/projects/project_service.go index 92d9083a90..a81f85d303 100644 --- a/internal/servers/controller/handlers/projects/project_service.go +++ b/internal/servers/controller/handlers/projects/project_service.go @@ -11,11 +11,19 @@ import ( pb "github.com/hashicorp/watchtower/internal/gen/controller/api/resources/scopes" pbs "github.com/hashicorp/watchtower/internal/gen/controller/api/services" "github.com/hashicorp/watchtower/internal/iam" + "google.golang.org/genproto/protobuf/field_mask" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) -var reInvalidID = regexp.MustCompile("[^A-Za-z0-9]") +var ( + reInvalidID = regexp.MustCompile("[^A-Za-z0-9]") + // TODO(ICU-28): Find a way to auto update these names and enforce the mappings between wire and storage. + wireToStorageMask = map[string]string{ + "name": "Name", + "description": "Description", + } +) type Service struct { pbs.UnimplementedProjectServiceServer @@ -107,22 +115,25 @@ func (s Service) createInRepo(ctx context.Context, req *pbs.CreateProjectRequest func (s Service) updateInRepo(ctx context.Context, req *pbs.UpdateProjectRequest) (*pb.Project, error) { item := req.GetItem() - // TODO: convert field masks from API field masks with snake_case to db field masks casing. - madeUp := []string{} opts := []iam.Option{iam.WithPublicId(req.GetId())} if desc := item.GetDescription(); desc != nil { - madeUp = append(madeUp, "Description") opts = append(opts, iam.WithDescription(desc.GetValue())) } if name := item.GetName(); name != nil { - madeUp = append(madeUp, "Name") opts = append(opts, iam.WithName(name.GetValue())) } p, err := iam.NewProject(req.GetOrgId(), opts...) if err != nil { return nil, err } - out, _, err := s.repo.UpdateScope(ctx, p, madeUp) + dbMask, err := toDbUpdateMask(req.GetUpdateMask()) + if err != nil { + return nil, err + } + if len(dbMask) == 0 { + return nil, status.Errorf(codes.InvalidArgument, "No valid fields included in the update mask.") + } + out, _, err := s.repo.UpdateScope(ctx, p, dbMask) if err != nil { return nil, err } @@ -132,6 +143,25 @@ func (s Service) updateInRepo(ctx context.Context, req *pbs.UpdateProjectRequest return toProto(out), nil } +// toDbUpdateMask converts the wire format's FieldMask into a list of strings containing FieldMask paths used +func toDbUpdateMask(fm *field_mask.FieldMask) ([]string, error) { + dbPaths := []string{} + invalid := []string{} + for _, p := range fm.GetPaths() { + for _, f := range strings.Split(p, ",") { + if dbField, ok := wireToStorageMask[strings.TrimSpace(f)]; ok { + dbPaths = append(dbPaths, dbField) + } else { + invalid = append(invalid, f) + } + } + } + if len(invalid) > 0 { + return nil, status.Errorf(codes.InvalidArgument, "Invalid fields passed in update_update mask: %v", invalid) + } + return dbPaths, nil +} + func toProto(in *iam.Scope) *pb.Project { out := pb.Project{Id: in.GetPublicId()} if in.GetDescription() != "" { @@ -194,7 +224,9 @@ func validateUpdateProjectRequest(req *pbs.UpdateProjectRequest) error { if err := validateID(req.GetOrgId(), "o_"); err != nil { return err } - // TODO: Either require mask to be set or document in API that an unset mask updates all fields. + if req.GetUpdateMask() == nil { + return status.Errorf(codes.InvalidArgument, "UpdateMask not provided but is required to update a project.") + } item := req.GetItem() if item == nil { // It is legitimate for no item to be specified in an update request as it indicates all fields provided in diff --git a/internal/servers/controller/handlers/projects/service_test.go b/internal/servers/controller/handlers/projects/service_test.go index 6e15ff48c6..026c5b42f6 100644 --- a/internal/servers/controller/handlers/projects/service_test.go +++ b/internal/servers/controller/handlers/projects/service_test.go @@ -14,6 +14,7 @@ import ( pbs "github.com/hashicorp/watchtower/internal/gen/controller/api/services" "github.com/hashicorp/watchtower/internal/iam" "github.com/hashicorp/watchtower/internal/servers/controller/handlers/projects" + "google.golang.org/genproto/protobuf/field_mask" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -42,7 +43,7 @@ func createDefaultProjectAndRepo(t *testing.T) (*iam.Scope, *iam.Repository) { t.Fatalf("Could not create org scope: %v", err) } - p, err := iam.NewProject(oRes.GetPublicId(), iam.WithName("default")) + p, err := iam.NewProject(oRes.GetPublicId(), iam.WithName("default"), iam.WithDescription("default")) if err != nil { t.Fatalf("Could not get new project: %v", err) } @@ -64,6 +65,7 @@ func TestGet(t *testing.T) { pProject := &pb.Project{ Id: proj.GetPublicId(), Name: &wrappers.StringValue{Value: proj.GetName()}, + Description: &wrappers.StringValue{Value: proj.GetDescription()}, CreatedTime: proj.CreateTime.GetTimestamp(), UpdatedTime: proj.UpdateTime.GetTimestamp(), } @@ -210,6 +212,15 @@ func TestCreate(t *testing.T) { func TestUpdate(t *testing.T) { proj, repo := createDefaultProjectAndRepo(t) + tested := projects.NewService(repo) + + var err error + resetProject := func() { + if proj, _, err = repo.UpdateScope(context.Background(), proj, []string{"Name", "Description"}); err != nil { + t.Fatalf("Failed to reset the project") + } + } + projCreated, err := ptypes.Timestamp(proj.GetCreateTime().GetTimestamp()) if err != nil { t.Fatalf("Error converting proto to timestamp: %v", err) @@ -227,10 +238,36 @@ func TestUpdate(t *testing.T) { }{ { name: "Update an Existing Project", - req: &pbs.UpdateProjectRequest{Item: &pb.Project{ - Name: &wrappers.StringValue{Value: "new"}, - Description: &wrappers.StringValue{Value: "desc"}, - }}, + req: &pbs.UpdateProjectRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"name", "description"}, + }, + Item: &pb.Project{ + Name: &wrappers.StringValue{Value: "new"}, + Description: &wrappers.StringValue{Value: "desc"}, + }, + }, + res: &pbs.UpdateProjectResponse{ + Item: &pb.Project{ + Id: proj.GetPublicId(), + Name: &wrappers.StringValue{Value: "new"}, + Description: &wrappers.StringValue{Value: "desc"}, + CreatedTime: proj.GetCreateTime().GetTimestamp(), + }, + }, + errCode: codes.OK, + }, + { + name: "Multiple Paths in single string", + req: &pbs.UpdateProjectRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"name,description"}, + }, + Item: &pb.Project{ + Name: &wrappers.StringValue{Value: "new"}, + Description: &wrappers.StringValue{Value: "desc"}, + }, + }, res: &pbs.UpdateProjectResponse{ Item: &pb.Project{ Id: proj.GetPublicId(), @@ -241,24 +278,122 @@ func TestUpdate(t *testing.T) { }, errCode: codes.OK, }, + { + name: "No Update Mask Is Invalid Argument", + req: &pbs.UpdateProjectRequest{ + Item: &pb.Project{ + Name: &wrappers.StringValue{Value: "updated name"}, + Description: &wrappers.StringValue{Value: "updated desc"}, + }, + }, + errCode: codes.InvalidArgument, + }, + { + name: "No Paths in Mask Is Invalid Argument", + req: &pbs.UpdateProjectRequest{ + UpdateMask: &field_mask.FieldMask{Paths: []string{}}, + Item: &pb.Project{ + Name: &wrappers.StringValue{Value: "updated name"}, + Description: &wrappers.StringValue{Value: "updated desc"}, + }, + }, + errCode: codes.InvalidArgument, + }, + { + name: "Only non-existant paths in Mask Is Invalid Argument", + req: &pbs.UpdateProjectRequest{ + UpdateMask: &field_mask.FieldMask{Paths: []string{"nonexistant_field"}}, + Item: &pb.Project{ + Name: &wrappers.StringValue{Value: "updated name"}, + Description: &wrappers.StringValue{Value: "updated desc"}, + }, + }, + errCode: codes.InvalidArgument, + }, + { + name: "Unset Name", + req: &pbs.UpdateProjectRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"name"}, + }, + Item: &pb.Project{ + Description: &wrappers.StringValue{Value: "ignored"}, + }, + }, + res: &pbs.UpdateProjectResponse{ + Item: &pb.Project{ + Id: proj.GetPublicId(), + Description: &wrappers.StringValue{Value: "default"}, + CreatedTime: proj.GetCreateTime().GetTimestamp(), + }, + }, + errCode: codes.OK, + }, + { + name: "Update Only Name", + req: &pbs.UpdateProjectRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"name"}, + }, + Item: &pb.Project{ + Name: &wrappers.StringValue{Value: "updated"}, + Description: &wrappers.StringValue{Value: "ignored"}, + }, + }, + res: &pbs.UpdateProjectResponse{ + Item: &pb.Project{ + Id: proj.GetPublicId(), + Name: &wrappers.StringValue{Value: "updated"}, + Description: &wrappers.StringValue{Value: "default"}, + CreatedTime: proj.GetCreateTime().GetTimestamp(), + }, + }, + errCode: codes.OK, + }, + { + name: "Update Only Description", + req: &pbs.UpdateProjectRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"description"}, + }, + Item: &pb.Project{ + Name: &wrappers.StringValue{Value: "ignored"}, + Description: &wrappers.StringValue{Value: "notignored"}, + }, + }, + res: &pbs.UpdateProjectResponse{ + Item: &pb.Project{ + Id: proj.GetPublicId(), + Name: &wrappers.StringValue{Value: "default"}, + Description: &wrappers.StringValue{Value: "notignored"}, + CreatedTime: proj.GetCreateTime().GetTimestamp(), + }, + }, + errCode: codes.OK, + }, { name: "Update a Non Existing Project", req: &pbs.UpdateProjectRequest{ Id: "p_DoesntExis", + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"description"}, + }, Item: &pb.Project{ Name: &wrappers.StringValue{Value: "new"}, Description: &wrappers.StringValue{Value: "desc"}, }, }, - // TODO: Update this to be NotFound. errCode: codes.Unknown, }, { name: "Cant change Id", req: &pbs.UpdateProjectRequest{ - Id: "p_1234567890", + Id: proj.GetPublicId(), + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"id"}, + }, Item: &pb.Project{ - Id: "p_0987654321", + Id: "p_somethinge", Name: &wrappers.StringValue{Value: "new"}, Description: &wrappers.StringValue{Value: "new desc"}, }}, @@ -267,33 +402,43 @@ func TestUpdate(t *testing.T) { }, { name: "Cant specify Created Time", - req: &pbs.UpdateProjectRequest{Item: &pb.Project{ - CreatedTime: ptypes.TimestampNow(), - }}, + req: &pbs.UpdateProjectRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"created_time"}, + }, + Item: &pb.Project{ + CreatedTime: ptypes.TimestampNow(), + }, + }, res: nil, errCode: codes.InvalidArgument, }, { name: "Cant specify Updated Time", - req: &pbs.UpdateProjectRequest{Item: &pb.Project{ - UpdatedTime: ptypes.TimestampNow(), - }}, + req: &pbs.UpdateProjectRequest{ + UpdateMask: &field_mask.FieldMask{ + Paths: []string{"updated_time"}, + }, + Item: &pb.Project{ + UpdatedTime: ptypes.TimestampNow(), + }, + }, res: nil, errCode: codes.InvalidArgument, }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { + defer resetProject() assert := assert.New(t) req := proto.Clone(toMerge).(*pbs.UpdateProjectRequest) proto.Merge(req, tc.req) - s := projects.NewService(repo) - - got, gErr := s.UpdateProject(context.Background(), req) + got, gErr := tested.UpdateProject(context.Background(), req) assert.Equal(tc.errCode, status.Code(gErr), "UpdateProject(%+v) got error %v, wanted %v", req, gErr, tc.errCode) if got != nil { + assert.NotNilf(tc.res, "Expected UpdateProject response to be nil, but was %v", got) gotUpdateTime, err := ptypes.Timestamp(got.GetItem().GetUpdatedTime()) if err != nil { t.Fatalf("Error converting proto to timestamp: %v", err) @@ -305,7 +450,7 @@ func TestUpdate(t *testing.T) { _ = projCreated // Clear all values which are hard to compare against. - got.Item.CreatedTime, got.Item.UpdatedTime, tc.res.Item.CreatedTime, tc.res.Item.UpdatedTime = nil, nil, nil, nil + got.Item.UpdatedTime, tc.res.Item.UpdatedTime = nil, nil } assert.True(proto.Equal(got, tc.res), "UpdateProject(%q) got response %q, wanted %q", req, got, tc.res) }) diff --git a/tools/tools.go b/tools/tools.go index 8fe3f1a2fb..a3b02aa86b 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -10,23 +10,28 @@ package tools -//go:generate go install github.com/bufbuild/buf/cmd/buf -import _ "github.com/bufbuild/buf/cmd/buf" +import ( + //go:generate go install github.com/bufbuild/buf/cmd/buf + _ "github.com/bufbuild/buf/cmd/buf" -//go:generate go install github.com/go-swagger/go-swagger/cmd/swagger -import _ "github.com/go-swagger/go-swagger/cmd/swagger" + //go:generate go install github.com/favadi/protoc-go-inject-tag + _ "github.com/favadi/protoc-go-inject-tag" -//go:generate go install github.com/favadi/protoc-go-inject-tag -import _ "github.com/favadi/protoc-go-inject-tag" + //go:generate go install github.com/go-swagger/go-swagger/cmd/swagger + _ "github.com/go-swagger/go-swagger/cmd/swagger" -// use this instead of google.golang.org/protobuf/cmd/protoc-gen-go since this supports grpc plugin while the other does not. -// see https://github.com/golang/protobuf/releases#v1.4-generated-code and -// https://github.com/protocolbuffers/protobuf-go/releases/tag/v1.20.0#v1.20-grpc-support -//go:generate go install github.com/golang/protobuf/protoc-gen-go -import _ "github.com/golang/protobuf/protoc-gen-go" + // use this instead of google.golang.org/protobuf/cmd/protoc-gen-go since this supports grpc plugin while the other does not. + // see https://github.com/golang/protobuf/releases#v1.4-generated-code and + // https://github.com/protocolbuffers/protobuf-go/releases/tag/v1.20.0#v1.20-grpc-support + //go:generate go install github.com/golang/protobuf/protoc-gen-go + _ "github.com/golang/protobuf/protoc-gen-go" -//go:generate go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway -import _ "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway" + //go:generate go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway + _ "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway" -//go:generate go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger -import _ "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger" + //go:generate go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger + _ "github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger" + + //go:generate go install golang.org/x/tools/cmd/goimports + _ "golang.org/x/tools/cmd/goimports" +)