github.com/kubeshop/testkube@v1.17.23/internal/app/api/v1/testsource.go (about) 1 package v1 2 3 import ( 4 "bytes" 5 "fmt" 6 "net/http" 7 8 "github.com/gofiber/fiber/v2" 9 "k8s.io/apimachinery/pkg/api/errors" 10 "k8s.io/apimachinery/pkg/util/yaml" 11 12 testsourcev1 "github.com/kubeshop/testkube-operator/api/testsource/v1" 13 "github.com/kubeshop/testkube-operator/pkg/client/testsources/v1" 14 "github.com/kubeshop/testkube-operator/pkg/secret" 15 "github.com/kubeshop/testkube/pkg/api/v1/testkube" 16 "github.com/kubeshop/testkube/pkg/crd" 17 "github.com/kubeshop/testkube/pkg/executor/client" 18 testsourcesmapper "github.com/kubeshop/testkube/pkg/mapper/testsources" 19 ) 20 21 func (s TestkubeAPI) CreateTestSourceHandler() fiber.Handler { 22 return func(c *fiber.Ctx) error { 23 errPrefix := "failed to create test source" 24 var testSource testsourcev1.TestSource 25 var secrets map[string]string 26 if string(c.Request().Header.ContentType()) == mediaTypeYAML { 27 testSourceSpec := string(c.Body()) 28 decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(testSourceSpec), len(testSourceSpec)) 29 if err := decoder.Decode(&testSource); err != nil { 30 return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse yaml request: %w", errPrefix, err)) 31 } 32 } else { 33 var request testkube.TestSourceUpsertRequest 34 err := c.BodyParser(&request) 35 if err != nil { 36 return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse json request: %s", errPrefix, err)) 37 } 38 39 if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { 40 if request.Data != "" { 41 request.Data = fmt.Sprintf("%q", request.Data) 42 } 43 44 data, err := crd.GenerateYAML(crd.TemplateTestSource, []testkube.TestSourceUpsertRequest{request}) 45 return s.getCRDs(c, data, err) 46 } 47 48 testSource = testsourcesmapper.MapAPIToCRD(request) 49 testSource.Namespace = s.Namespace 50 if request.Repository != nil && !s.disableSecretCreation { 51 secrets = createTestSecretsData(request.Repository.Username, request.Repository.Token) 52 } 53 } 54 55 created, err := s.TestSourcesClient.Create(&testSource, testsources.Option{Secrets: secrets}) 56 if err != nil { 57 return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not create test source: %w", errPrefix, err)) 58 } 59 60 c.Status(http.StatusCreated) 61 return c.JSON(created) 62 } 63 } 64 65 func (s TestkubeAPI) UpdateTestSourceHandler() fiber.Handler { 66 return func(c *fiber.Ctx) error { 67 errPrefix := "failed to update test source" 68 var request testkube.TestSourceUpdateRequest 69 if string(c.Request().Header.ContentType()) == mediaTypeYAML { 70 var testSource testsourcev1.TestSource 71 testSourceSpec := string(c.Body()) 72 decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewBufferString(testSourceSpec), len(testSourceSpec)) 73 if err := decoder.Decode(&testSource); err != nil { 74 return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse yaml request: %w", errPrefix, err)) 75 } 76 77 request = testsourcesmapper.MapSpecToUpdate(&testSource) 78 } else { 79 err := c.BodyParser(&request) 80 if err != nil { 81 return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse json jrequest: %s", errPrefix, err)) 82 } 83 } 84 85 var name string 86 if request.Name != nil { 87 name = *request.Name 88 } 89 errPrefix = errPrefix + " " + name 90 // we need to get resource first and load its metadata.ResourceVersion 91 testSource, err := s.TestSourcesClient.Get(name) 92 if err != nil { 93 if errors.IsNotFound(err) { 94 return s.Error(c, http.StatusNotFound, fmt.Errorf("%s: test source not found: %w", errPrefix, err)) 95 } 96 97 return s.Error(c, http.StatusBadGateway, err) 98 } 99 100 // map update test source but load spec only to not override metadata.ResourceVersion 101 testSourceSpec := testsourcesmapper.MapUpdateToSpec(request, testSource) 102 103 var option *testsources.Option 104 if request.Repository != nil && (*request.Repository) != nil { 105 username := (*request.Repository).Username 106 token := (*request.Repository).Token 107 if (username != nil || token != nil) && !s.disableSecretCreation { 108 data, err := s.SecretClient.Get(secret.GetMetadataName(name, client.SecretSource)) 109 if err != nil && !errors.IsNotFound(err) { 110 return s.Error(c, http.StatusBadGateway, err) 111 } 112 113 option = &testsources.Option{Secrets: updateTestSecretsData(data, username, token)} 114 } 115 } 116 117 var updatedTestSource *testsourcev1.TestSource 118 if option != nil { 119 updatedTestSource, err = s.TestSourcesClient.Update(testSourceSpec, *option) 120 } else { 121 updatedTestSource, err = s.TestSourcesClient.Update(testSourceSpec) 122 } 123 124 if err != nil { 125 return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client coult not update test source: %w", errPrefix, err)) 126 } 127 128 return c.JSON(updatedTestSource) 129 } 130 } 131 132 func (s TestkubeAPI) ListTestSourcesHandler() fiber.Handler { 133 return func(c *fiber.Ctx) error { 134 errPrefix := "failed to list test sources" 135 136 list, err := s.TestSourcesClient.List(c.Query("selector")) 137 if err != nil { 138 return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not list test sources: %s", errPrefix, err)) 139 } 140 141 results := []testkube.TestSource{} 142 for _, item := range list.Items { 143 results = append(results, testsourcesmapper.MapCRDToAPI(item)) 144 } 145 146 if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { 147 for i := range results { 148 if results[i].Data != "" { 149 results[i].Data = fmt.Sprintf("%q", results[i].Data) 150 } 151 } 152 153 data, err := crd.GenerateYAML(crd.TemplateTestSource, results) 154 return s.getCRDs(c, data, err) 155 } 156 157 return c.JSON(results) 158 } 159 } 160 161 func (s TestkubeAPI) GetTestSourceHandler() fiber.Handler { 162 return func(c *fiber.Ctx) error { 163 name := c.Params("name") 164 errPrefix := "failed to get test source" + name 165 166 item, err := s.TestSourcesClient.Get(name) 167 if err != nil { 168 if errors.IsNotFound(err) { 169 return s.Error(c, http.StatusNotFound, fmt.Errorf("%s: client could not find test source: %w", errPrefix, err)) 170 } 171 return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not get test source: %w", errPrefix, err)) 172 } 173 174 result := testsourcesmapper.MapCRDToAPI(*item) 175 if c.Accepts(mediaTypeJSON, mediaTypeYAML) == mediaTypeYAML { 176 if result.Data != "" { 177 result.Data = fmt.Sprintf("%q", result.Data) 178 } 179 180 data, err := crd.GenerateYAML(crd.TemplateTestSource, []testkube.TestSource{result}) 181 return s.getCRDs(c, data, err) 182 } 183 184 return c.JSON(result) 185 } 186 } 187 188 func (s TestkubeAPI) DeleteTestSourceHandler() fiber.Handler { 189 return func(c *fiber.Ctx) error { 190 name := c.Params("name") 191 errPrefix := "failed to delete test source" + name 192 193 err := s.TestSourcesClient.Delete(name) 194 if err != nil { 195 return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not delete test source: %w", errPrefix, err)) 196 } 197 198 c.Status(http.StatusNoContent) 199 return nil 200 } 201 } 202 203 func (s TestkubeAPI) DeleteTestSourcesHandler() fiber.Handler { 204 return func(c *fiber.Ctx) error { 205 errPrefix := "failed to delete test sources" 206 err := s.TestSourcesClient.DeleteByLabels(c.Query("selector")) 207 if err != nil { 208 return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not delete test sources: %w", errPrefix, err)) 209 } 210 211 c.Status(http.StatusNoContent) 212 return nil 213 } 214 } 215 216 func (s TestkubeAPI) ProcessTestSourceBatchHandler() fiber.Handler { 217 return func(c *fiber.Ctx) error { 218 errPrefix := "failed to batch process test sources" 219 220 var request testkube.TestSourceBatchRequest 221 err := c.BodyParser(&request) 222 if err != nil { 223 return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: could not parse request: %s", errPrefix, err)) 224 } 225 226 testSourceBatch := make(map[string]testkube.TestSourceUpsertRequest, len(request.Batch)) 227 for _, item := range request.Batch { 228 if _, ok := testSourceBatch[item.Name]; ok { 229 return s.Error(c, http.StatusBadRequest, fmt.Errorf("%s: test source with duplicated id/name %s", errPrefix, item.Name)) 230 } 231 232 testSourceBatch[item.Name] = item 233 } 234 235 list, err := s.TestSourcesClient.List("") 236 if err != nil { 237 return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not list test sources: %w", errPrefix, err)) 238 } 239 240 testSourceMap := make(map[string]testsourcev1.TestSource, len(list.Items)) 241 for _, item := range list.Items { 242 testSourceMap[item.Name] = item 243 } 244 245 var result testkube.TestSourceBatchResult 246 for name, item := range testSourceBatch { 247 testSource := testsourcesmapper.MapAPIToCRD(item) 248 var username, token string 249 if item.Repository != nil && !s.disableSecretCreation { 250 username = item.Repository.Username 251 token = item.Repository.Token 252 } 253 254 if existed, ok := testSourceMap[name]; !ok { 255 testSource.Namespace = s.Namespace 256 257 created, err := s.TestSourcesClient.Create(&testSource, testsources.Option{Secrets: getTestSourceSecretsData(username, token)}) 258 if err != nil { 259 return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not create test source %s: %w", errPrefix, testSource.Name, err)) 260 } 261 262 result.Created = append(result.Created, created.Name) 263 } else { 264 existed.Spec = testSource.Spec 265 existed.Labels = item.Labels 266 267 updated, err := s.TestSourcesClient.Update(&existed, testsources.Option{Secrets: getTestSourceSecretsData(username, token)}) 268 if err != nil { 269 return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not update test source %s: %w", errPrefix, testSource.Name, err)) 270 } 271 272 result.Updated = append(result.Updated, updated.Name) 273 } 274 } 275 276 for name := range testSourceMap { 277 if _, ok := testSourceBatch[name]; !ok { 278 err := s.TestSourcesClient.Delete(name) 279 if err != nil { 280 return s.Error(c, http.StatusBadGateway, fmt.Errorf("%s: client could not delete test source %s: %w", errPrefix, name, err)) 281 } 282 283 result.Deleted = append(result.Deleted, name) 284 } 285 } 286 287 return c.JSON(result) 288 } 289 } 290 291 func getTestSourceSecretsData(username, token string) map[string]string { 292 if username == "" && token == "" { 293 return nil 294 } 295 296 data := make(map[string]string, 0) 297 if username != "" { 298 data[client.GitUsernameSecretName] = username 299 } 300 301 if token != "" { 302 data[client.GitTokenSecretName] = token 303 } 304 305 return data 306 }