diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml new file mode 100644 index 00000000..31fb014b --- /dev/null +++ b/.github/workflows/automerge.yml @@ -0,0 +1,23 @@ +# .github/workflows/automerge.yml + +name: Dependabot (auto-merge) + +on: + pull_request: + branches: + - main + +permissions: + contents: write + pull-requests: write + +jobs: + dependabot: + runs-on: ubuntu-latest + if: ${{ github.actor == 'dependabot[bot]' }} + steps: + - name: Enable auto-merge for Dependabot PRs + run: gh pr merge --auto --merge "$PR_URL" + env: + PR_URL: ${{github.event.pull_request.html_url}} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..d0f548fc --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,47 @@ +# .github/workflows/main.yml + +name: Test & Lint + +on: + workflow_call: + push: + branches: + - main + - release/* + pull_request: + branches: + - main + +jobs: + test: + runs-on: ubuntu-latest + permissions: + checks: write + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: ./go.mod + - run: make test + - uses: dorny/test-reporter@v1 + if: success() || failure() + with: + name: Go Test Results + path: .test/reports/**-test.xml + reporter: java-junit + fail-on-error: 'true' + - uses: actions/upload-artifact@v4 + if: success() || failure() + with: + name: Test Reports + path: .test/reports/** + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: ./go.mod + check-latest: true + - run: make lint diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..b3b623d7 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,28 @@ +# .github/workflows/release.yml + +name: Release + +on: + push: + tags: + - 'v*' + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + test: + permissions: + checks: write + uses: ./.github/workflows/main.yml + + release: + runs-on: ubuntu-latest + needs: [ test ] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: ./go.mod + - run: make release + if: success() diff --git a/Makefile b/Makefile index 29b487b2..015be033 100644 --- a/Makefile +++ b/Makefile @@ -14,8 +14,8 @@ build: ## Build the binary file. generate: ## Generate code. $(GO) generate ./... $(GO_RUN_TOOLS) github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -config ./api/config.models.yml ./api/api.yml - $(GO_RUN_TOOLS) github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -config ./api/config.client.yml ./api/api.yml - $(GO_RUN_TOOLS) github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -config ./api/config.server.yml ./api/api.yml + $(GO_RUN_TOOLS) github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -config ./api/config.client.yml ./api/api.yml + $(GO_RUN_TOOLS) github.com/deepmap/oapi-codegen/v2/cmd/oapi-codegen -config ./api/config.server.yml ./api/api.yml .PHONY: fmt fmt: ## Run go fmt against code. diff --git a/api/api.yml b/api/api.yml index 3be6d92f..e24cac37 100644 --- a/api/api.yml +++ b/api/api.yml @@ -1,4 +1,4 @@ -openapi: 3.0.0 +openapi: 3.1.0 info: version: 1.0.0 @@ -54,6 +54,8 @@ paths: parameters: - $ref: '#/components/parameters/offsetParam' - $ref: '#/components/parameters/limitParam' + security: + - bearerAuth: ['read:teams'] responses: '200': description: Successfull response @@ -184,6 +186,10 @@ components: $ref: '#/components/schemas/Team' securitySchemes: + cookieAuth: + type: apiKey + in: cookie + name: JSESSIONID bearerAuth: type: http scheme: bearer @@ -207,6 +213,41 @@ components: type: array items: + System: + type: object + required: + - name + properties: + id: + type: string + format: uuid + readOnly: true + x-oapi-codegen-extra-tags: + gorm: "type:uuid;default:gen_random_uuid()" + name: + type: string + description: Name of the system + example: "eu-west-1" + description: + type: string + description: A description of the system. + createdAt: + type: string + format: date-time + description: Creation date and time + example: "2021-01-30T08:30:00Z" + updatedAt: + type: string + format: date-time + description: Creation date and time + example: "2021-01-30T08:30:00Z" + deletedAt: + type: string + format: date-time + description: Creation date and time + example: "2021-01-30T08:30:00Z" + + Team: type: object required: @@ -251,16 +292,7 @@ components: type: string date: type: string - System: - type: object - required: - - id - - name - properties: - id: - type: string - name: - type: string + Systems: type: array maxItems: 100 @@ -268,5 +300,6 @@ components: $ref: "#/components/schemas/System" security: - - bearer_auth: [] + - cookieAuth: [] + - bearerAuth: [] - api_key: [] diff --git a/api/config.models.yml b/api/config.models.yml index a9e6340c..ae331af3 100644 --- a/api/config.models.yml +++ b/api/config.models.yml @@ -2,6 +2,6 @@ package: api generate: models: true embedded-spec: true -output: api/models.gen.go +output: examples/api/models.gen.go output-options: skip-prune: true diff --git a/api/models.gen.go b/api/models.gen.go index d526809e..eef01b3e 100644 --- a/api/models.gen.go +++ b/api/models.gen.go @@ -19,6 +19,7 @@ import ( const ( Api_keyScopes = "api_key.Scopes" + BearerAuthScopes = "bearerAuth.Scopes" Bearer_authScopes = "bearer_auth.Scopes" ) @@ -32,8 +33,21 @@ type PaginatedResult struct { // System defines model for System. type System struct { - Id string `json:"id"` + // CreatedAt Creation date and time + CreatedAt *time.Time `json:"createdAt,omitempty"` + + // DeletedAt Creation date and time + DeletedAt *time.Time `json:"deletedAt,omitempty"` + + // Description A description of the system. + Description *string `json:"description,omitempty"` + Id *openapi_types.UUID `gorm:"type:uuid;default:gen_random_uuid()" json:"id,omitempty"` + + // Name Name of the system Name string `json:"name"` + + // UpdatedAt Creation date and time + UpdatedAt *time.Time `json:"updatedAt,omitempty"` } // Systems defines model for Systems. @@ -86,29 +100,30 @@ type CreateTeamJSONRequestBody = Team // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/8RXW2/bNhT+KwS7hw2QLSXFgEJ7cruu8Fa0QWN0wFwjoKVji61EquRREyHQfx8OqZtt", - "uQ2wdH0xLF7O5fvOjfc80UWpFSi0PL7npTCiAATjvnJZSLyiJfpKwSZGlii14jFfZcBUVWzBWKZ3TCIU", - "lqFmBrAyas4DLunY5wpMzQOuRAE89hJ5wG2SQSG81J2ocuTxZRTwQtzJoip4/Ct9SOU/LgKOdUnXpULY", - "g+FNE3C921n4tnUHxtlPsmRb2GkDzKIwKNWe1hOd55AgwwyYAVvlyCzgOSe85mkvxnZHE3Y3ATfwuQKL", - "z3UqwcH8woBAWIF3JNEKQSH9FWWZy0SQT+FHS47dj3T+ZGDHY/4kHCgM/a4NnbCmcfraNbpyJfZSCYT0", - "nXPSMW50CQZbUzw/8X1nuMeQ93BPbnnE7GhPGCNq2kKNIp+41PTQ6O1HSJDOXtcWoTi1SaYjARaNVHs6", - "7sk42WgBlgZSHq/pcnt0c1al19L9+RqsrYmNi9Slv3ERDTz3fndkHnpC1IoEXxZC5pM+JS4S0gWeRrQL", - "EqkVSwUCEyplKAvgAYc7UZQ5ybmMLi9m0cXsabSKnsVPoziK/uEB32lTCOQxp5uz9taJ6hRy+GGqR9om", - "UPEB0MuqKkeqAZG+VXnNYzQVHIsN+N1Mi1LOEp3CHtQM7tCIGYq9Y2KvTcFjdycmeb+1CRzvQd0YoVJd", - "3ND6z7+4iDoTawGvyvTHEHYU5mcj/D0Y2+J6GIske9KlL8ONr+vsDgZe1ql2qj2QVEZifU0J5BWLUt58", - "gtrlHOGUgUjBDNW12x+SqpR/gcuqLQgDZlFhRrf91x8dWH/+vepKMt3yu4OUDLH09TfRylbOmPVphd1Q", - "OO6kkrRkh4Lr87cv1O/AllpZeIRqdQwaBbza6dOAesKWCo1Oq4QWPqgPirrcqi4zrRVbXC2ZyHN9a1mt", - "K2pqbxar67nUH4ihXCbQmtvCvChFkgG7nEc84JXJW4hsHIa3t7dz4bbn2uzD9q4NXy9fvHxz/XJ2OY/m", - "GRa5K/ASXSiPzOCjGOIX82geuf5RghKl5DF/6pYCXgrMHGShHcrwHiZS6Z2bKSwTLJcWqaMXQok9pKy9", - "SZ2aSHBELlMe89fSYlfdXYdydDkVl1H0aH22U+FoOzR6wcqu27JOP5ku8rwz26dIVRTC1K3NbnvCO1+4", - "1l2T3NDNDrfw3v9Zps1jQnid6dtWXXAwGK6npi2ZklAaoLzAdhY0Er5AN0cR40Oid0bzcVnxtXwA/zhf", - "NtNk/n/Q2xZ7BFGE9/R7CHuPIY9pyQ0Cx8i+8usrd/kU3AmssDt6Hqnj/vhA5B513DzOgesqScDaXZUf", - "4f0KkGKR3GLbmi1/5z2odgrMPqdbPI8Qm7JtOBKOnwpN8M3jo2cPoVa6mnu+X/wnVEWev92d9aHD93hu", - "JycO285oBn/QJOsZOx5cT9vR5qus5n16ncsnz+iQRE7vpgl4qS36F4f65Kxtk+J5vUxPqR8y6fCF2uZF", - "TL56Q+ZbndZPQpn6eDwUM3ppPYTX7p1Wn0Pz4CkXjqQ3J2Fx8d2TzatPj5jwq5RsCm4dHadsUOqNxr7J", - "5HvfT3vfrYp0Krp3a+9D17youySVMaCQtfZ2TWdxtTyo1WC+gOE+er9FtO2G07WfEjfD3Ooy04+SN8LN", - "nesNJV8/xK43jTtO6nwtGmapOAxznYg80xbjZ9GzyJWT1sSJHmqBCQPOnXZ2Y5gJdItdX9rW3cQ3H7pD", - "/yztp7s2CPvvFo9m0/wbAAD//0gBizzyEQAA", + "H4sIAAAAAAAC/+xYUa/TuBL+K5G5D/dKaZMedCWU+1S4LOougiNasdKW6shNpq0hsYM9oSc66n9fjR0n", + "aZoC0sKyK+1L1dgZz8w3882M88BSVZRKgkTDkgdWcs0LQND2KReFwFtaoqcMTKpFiUJJlrDVAQJZFVvQ", + "JlC7QCAUJkAVaMBKyykLmaDXPlagaxYyyQtgiTuRhcykByi4O3XHqxxZchOHrOD3oqgKlvyXHoR0D7OQ", + "YV2SuJAIe9DsdAqZ2u0MfNm6M+PMB1EGW9gpDYFBrlHIPa2nKs8hxQAPEGgwVY6BAbzmhNM87kXf7njE", + "7lPINHyswOBTlQmwMD/TwBFW4BxJlUSQSH95WeYi5eRT9N6QYw89nf/SsGMJexR1IYzcronsYaeT1des", + "kcgt3wvJEbI31kkbca1K0NiY4uKTPHjDHYashXt0yyFmentca17TFirk+YjQqYVGbd9DivTusjYIxaVN", + "qYUnm+NlmC1yQskg4wgBl1mAogAWMrjnRZnT+TfxzWwSzyaP41X8JHkcJ3H8GwvZTumCI0sYSU4aqcYk", + "g1rIPZmUQQ4/THVP21D5POg9U4ZT4hqL33TsMJHRGa3iqhIZo7jx7LXMa5agrmAoFrL7ieKlmKQqgz3I", + "Cdyj5hPkexuVvdIFS6xMQuf9r6FAsgd5p7nMVHFH6//+j422o87Qj1e8gHPzzwCEanIEg5PZmE9Vmf2Y", + "tGgoLDRkLFk7zzZX89mCJfyfz3G2yf+TLYMLJzGLuyLSkspXigFNlESe4vOCiz7jOsD+4dEYKn8davz9", + "MvwtaNPgep6LdPaoS586ic/r9C+G7qxL7dTYIK20wHpJBHKKeSnuPkBtOUc4HYBnoLvW7fc7UpXiF7Cs", + "2gLXoOcVHkjaPf3kwfr515Xv9yTldrtTDoila+6pkqayxqwv2/eG0nEnpKAl03XzXpsjbr8BUypp4BJX", + "l6wXqF7JoMsmS0tC7tRlQj0KFhK1yqqUFt7Jd5JGqFVdHpSSwfx2EfA8V0cT1KqiienVfLWcCvWOIpSL", + "FBpzG5jnJU8PENxMYxaySucNRCaJouPxOOV2e6r0PmpkTfRy8ez5q+Xzyc00nh6wyO30INCmcs8M1ssh", + "NpvG09gOJyVIXgqWsMfTmVVacjxYyCLTleE9jFDpjR1YTcCDXBikblRwyfeQNR3JUEelINhALjKWsJfC", + "oK/udvyx4bIqbuL4mw1xXoUN23AAKP0oF3j9ZDrPc2+2o0hVFFzXjc12e8Q7V7jWfgLbkKTHLXpwfxbZ", + "6VtCuDyo49J3/P6tYz02yovsfEpoLhpawCfwQzpFvCO6N5r1y4qr5R34Q75sxoP550FvGuwReBE90O85", + "7C2GLKElOwgMkX3h1ldW+BLcEazQv3odqWF//ErkvuldZsiBZZWmYMyuygd4vwCkXCS3gm0dLP7PWlDN", + "GJgtpxs8B4iN2da9EvXvoafwi6/37tSEWmlr7vV+8YdQ5Xn+enfVB4/v8FJITpy3nd4F76smWRex4eB6", + "2Y42n41q3tLrrNtbf/rNem2ntcSFd0OgjlDP7XZ8syZuTiErlUF385UfrGMNf57Wi+wySzrSnX8paSiU", + "ECzO5ulWZfWjSGQudc+P6d34vyYF/PeC+hrwZ58Uot7pp4sMmn13Xjr12YCUbpV4KeFow3EZDWJpb0Ic", + "5enbdjD8bgXHq/DfT1offJ+jRpRWWoPEoLHX96f57eKsrIP+BJq5RP9SoI2fY9duoNyMJv0dd1m/IZ62", + "8+7aJb5V58pWN3YlUZSrlOcHZTB5Ej+JbeVpTBxptwYCrsG604x5AR442kXfwra1Hw6nXSNpb7DtINgk", + "Yfvc4HHanH4PAAD//x6dYEp6FAAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/server.gen.go b/api/server.gen.go index cac93944..4cac5be4 100644 --- a/api/server.gen.go +++ b/api/server.gen.go @@ -97,9 +97,7 @@ func (siw *ServerInterfaceWrapper) ListTeam(c *fiber.Ctx) error { var err error - c.Context().SetUserValue(Bearer_authScopes, []string{}) - - c.Context().SetUserValue(Api_keyScopes, []string{}) + c.Context().SetUserValue(BearerAuthScopes, []string{"read:teams"}) // Parameter object where we will unmarshal all parameters from the context var params ListTeamParams diff --git a/api/sources/register.go b/api/sources/register.go index 055d41ef..49dc572c 100644 --- a/api/sources/register.go +++ b/api/sources/register.go @@ -10,3 +10,9 @@ var WebhookResource = schema.GroupResource{ Group: GroupName, Resource: "webhook", } + +// HTTPResource ... +var HTTPResource = schema.GroupResource{ + Group: GroupName, + Resource: "http", +} diff --git a/cmd/api/main.go b/cmd/api/main.go index f0a4a4fc..e1203892 100644 --- a/cmd/api/main.go +++ b/cmd/api/main.go @@ -13,7 +13,6 @@ import ( "github.com/katallaxie/pkg/logger" "github.com/katallaxie/pkg/server" - models "github.com/zeiss/typhoon/api" "github.com/spf13/cobra" "gorm.io/driver/postgres" @@ -62,7 +61,10 @@ func run(ctx context.Context) error { return err } - conn.AutoMigrate(&models.Team{}) + err = config.RunMigrations(conn) + if err != nil { + return err + } db := adapter.NewDB(conn) srv, _ := server.WithContext(ctx) diff --git a/internal/config/migration.go b/internal/config/migration.go new file mode 100644 index 00000000..79752a95 --- /dev/null +++ b/internal/config/migration.go @@ -0,0 +1,16 @@ +package config + +import ( + "github.com/zeiss/typhoon/internal/models" + "gorm.io/gorm" +) + +// RunMigrations ... +func RunMigrations(db *gorm.DB) error { + return db.AutoMigrate( + &models.Role{}, + &models.Team{}, + &models.User{}, + &models.UserRole{}, + ) +} diff --git a/internal/models/teams.go b/internal/models/teams.go index 813a407a..e4c466c7 100644 --- a/internal/models/teams.go +++ b/internal/models/teams.go @@ -1,9 +1,113 @@ package models import ( + "time" + + "github.com/google/uuid" openapi "github.com/zeiss/typhoon/api" + "gorm.io/gorm" ) +// Team ... +type Team struct { + ID uuid.UUID `gorm:"type:uuid;default:gen_random_uuid()"` + Name string + Description *string + Systems *[]System `gorm:"many2many:team_systems;"` + + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt time.Time + + gorm.Model +} + +// User ... +type User struct { + ID uuid.UUID `gorm:"type:uuid;default:gen_random_uuid()"` + Name string + + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt time.Time +} + +// Role ... +type Role struct { + ID uuid.UUID `gorm:"type:uuid;default:gen_random_uuid()"` + Name string + + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt time.Time + + gorm.Model +} + +// Permission ... +type Permission struct { + ID uint `gorm:"primaryKey"` + Slug string + Description *string + + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt time.Time + + gorm.Model +} + +// RolePermission ... +type RolePermission struct { + ID uint `gorm:"primaryKey"` + + RoleID uuid.UUID + Role Role + + PermissionID uuid.UUID + Permission Permission +} + +// UserRole ... +type UserRole struct { + ID uint `gorm:"primaryKey"` + + UserID uuid.UUID + User User + + RoleID uuid.UUID + Role Role + + TeamID uint + Team Team + + gorm.Model +} + +// UserTeam ... +type UserTeam struct { + ID uint `gorm:"primaryKey"` + + UserID uuid.UUID + User User + + TeamID uint + Team Team +} + +// System ... +type System struct { + ID uuid.UUID `gorm:"type:uuid;default:gen_random_uuid()"` + Name string + Description *string + + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt time.Time + + gorm.Model +} + // PaginatedListTeams ... type PaginatedListTeams struct { Limit *float32 `json:"limit,omitempty"` diff --git a/internal/services/api/api.go b/internal/services/api/api.go index 8462896d..b11c6a7d 100644 --- a/internal/services/api/api.go +++ b/internal/services/api/api.go @@ -2,6 +2,7 @@ package api import ( "context" + "fmt" openapi "github.com/zeiss/typhoon/api" "github.com/zeiss/typhoon/internal/adapter" @@ -46,6 +47,8 @@ func (a *ApiSrv) Start(ctx context.Context, ready server.ReadyFunc, run server.R validatorOptions := &middleware.Options{} validatorOptions.Options.AuthenticationFunc = func(ctx context.Context, filter *openapi3filter.AuthenticationInput) error { + fmt.Println(filter) + return nil } diff --git a/pkg/sinks/adapter/nats/nats.go b/pkg/sinks/adapter/nats/nats.go index 5be7a816..af95aec8 100644 --- a/pkg/sinks/adapter/nats/nats.go +++ b/pkg/sinks/adapter/nats/nats.go @@ -32,7 +32,7 @@ type natsAdapter struct { mt *kadapter.MetricTag } -// NewSink ... +// NewSink returns a new NATS.io adapter func NewSink(ctx context.Context, envAcc kadapter.EnvConfigAccessor, client cloudevents.Client) kadapter.Adapter { logger := logging.FromContext(ctx) @@ -49,7 +49,7 @@ func NewSink(ctx context.Context, envAcc kadapter.EnvConfigAccessor, client clou } } -// Start ... +// Start is called to start the NATS.io adapter. func (a *natsAdapter) Start(ctx context.Context) error { a.logger.Info("starting NATS.io adapter") diff --git a/pkg/sources/adapter/poller/env.go b/pkg/sources/adapter/poller/env.go new file mode 100644 index 00000000..ddf5da90 --- /dev/null +++ b/pkg/sources/adapter/poller/env.go @@ -0,0 +1,27 @@ +package poller + +import ( + "time" + + kadapter "knative.dev/eventing/pkg/adapter/v2" +) + +// NewEnvConfig ... +func NewEnvconfig() kadapter.EnvConfigAccessor { + return &config{} +} + +type config struct { + kadapter.EnvConfig + + EventType string `envconfig:"HTTP_EVENT_TYPE" required:"true"` + EventSource string `envconfig:"HTTP_EVENT_SOURCE" required:"true"` + URL string `envconfig:"HTTP_URL" required:"true"` + Method string `envconfig:"HTTP_METHOD" required:"true"` + SkipVerify bool `envconfig:"HTTP_SKIP_VERIFY"` + CACertificate string `envconfig:"HTTP_CA_CERTIFICATE"` + BasicAuthUsername string `envconfig:"HTTP_BASICAUTH_USERNAME"` + BasicAuthPassword string `envconfig:"HTTP_BASICAUTH_PASSWORD"` + Headers map[string]string `envconfig:"HTTP_HEADERS"` + Interval time.Duration `envconfig:"HTTP_INTERVAL" required:"true"` +} diff --git a/pkg/sources/adapter/poller/poller.go b/pkg/sources/adapter/poller/poller.go new file mode 100644 index 00000000..40a9c25b --- /dev/null +++ b/pkg/sources/adapter/poller/poller.go @@ -0,0 +1,122 @@ +package poller + +import ( + "context" + "crypto/tls" + "errors" + "io" + "net/http" + "time" + + "github.com/zeiss/typhoon/api/sources" + + cloudevents "github.com/cloudevents/sdk-go/v2" + "go.uber.org/zap" + kadapter "knative.dev/eventing/pkg/adapter/v2" + logging "knative.dev/pkg/logging" +) + +var _ kadapter.Adapter = (*pollerAdapter)(nil) + +type pollerAdapter struct { + logger *zap.SugaredLogger + metricTag *kadapter.MetricTag + + client *http.Client + ce cloudevents.Client + cfg *config +} + +// NewAdapter ... +func NewAdapter(ctx context.Context, env kadapter.EnvConfigAccessor, client cloudevents.Client) kadapter.Adapter { + logger := logging.FromContext(ctx) + + mt := &kadapter.MetricTag{ + ResourceGroup: sources.HTTPResource.String(), + Namespace: env.GetNamespace(), + Name: env.GetName(), + } + + cfg := env.(*config) + + t := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + + c := &http.Client{Transport: t} + + return &pollerAdapter{ + logger: logger, + metricTag: mt, + client: c, + ce: client, + cfg: cfg, + } +} + +// Start ... +func (h *pollerAdapter) Start(ctx context.Context) error { + h.logger.Infow("Starting poller", "event-source", h.cfg.EventSource, "event-type", h.cfg.EventType, "url", h.cfg.URL, "interval", h.cfg.Interval) + + ctx = kadapter.ContextWithMetricTag(ctx, h.metricTag) + + req, err := http.NewRequest(h.cfg.Method, h.cfg.URL, nil) + if err != nil { + return err + } + + if h.cfg.BasicAuthUsername != "" && h.cfg.BasicAuthPassword != "" { + req.SetBasicAuth(h.cfg.BasicAuthUsername, h.cfg.BasicAuthPassword) + } + + for k, v := range h.cfg.Headers { + req.Header.Set(k, v) + } + + t := time.NewTicker(h.cfg.Interval) + + for { + select { + case <-ctx.Done(): + return nil + case <-t.C: + err := h.dispatch(ctx, req) + if err != nil { + return err + } + } + } +} + +func (h *pollerAdapter) dispatch(ctx context.Context, req *http.Request) error { + res, err := h.client.Do(req) + if err != nil { + return err + } + + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return err + } + + if res.StatusCode != http.StatusOK { + return errors.New("non-200 status code") + } + + event := cloudevents.NewEvent(cloudevents.VersionV1) + event.SetType(h.cfg.EventType) + event.SetSource(h.cfg.EventSource) + + err = event.SetData(cloudevents.ApplicationJSON, body) + if err != nil { + return err + } + + r := h.ce.Send(ctx, event) + if r != nil && !cloudevents.IsACK(r) { + return errors.New("failed to send event") + } + + return nil +} diff --git a/pkg/sources/adapter/webhook/env.go b/pkg/sources/adapter/webhook/env.go index bbc60eec..72aa31e8 100644 --- a/pkg/sources/adapter/webhook/env.go +++ b/pkg/sources/adapter/webhook/env.go @@ -20,6 +20,7 @@ type env struct { EventType string `envconfig:"WEBHOOK_EVENT_TYPE" required:"true"` } +// ExtensionAttributesFrom ... type ExtensionAttributesFrom struct { method bool path bool diff --git a/pkg/sources/adapter/webhook/webhook.go b/pkg/sources/adapter/webhook/webhook.go index 7c682ea5..508b50a6 100644 --- a/pkg/sources/adapter/webhook/webhook.go +++ b/pkg/sources/adapter/webhook/webhook.go @@ -3,8 +3,10 @@ package webhook import ( "context" - cloudevents "github.com/cloudevents/sdk-go/v2" "github.com/zeiss/typhoon/api/sources" + + cloudevents "github.com/cloudevents/sdk-go/v2" + "github.com/gofiber/fiber/v2" "go.uber.org/zap" kadapter "knative.dev/eventing/pkg/adapter/v2" logging "knative.dev/pkg/logging" @@ -16,6 +18,9 @@ type webhookAdapter struct { logger *zap.SugaredLogger client cloudevents.Client metricTag *kadapter.MetricTag + + eventType string + eventSource string } // NewAdapter ... @@ -37,16 +42,47 @@ func NewAdapter(ctx context.Context, env kadapter.EnvConfigAccessor, client clou } // Start ... -func (a *webhookAdapter) Start(ctx context.Context) error { - ctx = kadapter.ContextWithMetricTag(ctx, a.metricTag) +func (h *webhookAdapter) Start(ctx context.Context) error { + ctx = kadapter.ContextWithMetricTag(ctx, h.metricTag) + + app := fiber.New() + app.Post("/", h.HandleAll) + app.Get("/healthz", h.HandleHealthz) + + err := app.Listen(":3000") + if err != nil { + return err + } + + return nil +} + +// HandleHealthz ... +func (h *webhookAdapter) HandleHealthz(c *fiber.Ctx) error { + return c.SendStatus(fiber.StatusOK) +} - // m := http.NewServeMux() - // m.HandleFunc("/", h.handleAll(ctx)) +// HandleAll ... +func (h *webhookAdapter) HandleAll(c *fiber.Ctx) error { + logging.FromContext(c.Context()).Info("Received request") - // s := &http.Server{ - // Addr: fmt.Sprintf(":%d", serverPort), - // Handler: m, - // } + event := cloudevents.NewEvent(cloudevents.VersionV1) + event.SetType(h.eventType) + event.SetSource(h.eventSource) + + err := event.SetData(c.Get("Content-Type"), c.Body()) + if err != nil { + return err + } + + e, err := h.client.Request(c.Context(), event) + if err != nil && !cloudevents.IsACK(err) { + return err + } + + if e == nil || e.Data() == nil { + return c.SendStatus(fiber.StatusNoContent) + } return nil }