Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f21e90f
migration
Intrpt Jun 15, 2024
295ee36
updated model + gameRepository
Intrpt Jun 15, 2024
03d5327
updated repository tests
Intrpt Jun 15, 2024
9212673
set filename when creating a game
Intrpt Jun 15, 2024
d8e6b1e
Fix loadbalancer IP fetching in operator
AustrianGam3r Jun 15, 2024
fa80934
Fix panic
AustrianGam3r Jun 15, 2024
da6b7b1
Add additional node
AustrianGam3r Jun 15, 2024
42c9032
Change upgrade parameters
AustrianGam3r Jun 15, 2024
8d66a76
Add LoadBalancer Mutator to keep stunner ips in tailscale
AustrianGam3r Jun 15, 2024
5316121
Remove namespace of tailscale operator
AustrianGam3r Jun 15, 2024
5f814a2
Add storage class and kubernetes permissions
AustrianGam3r Jun 16, 2024
4484cd2
UserAssigned identities for cluster
rieglerthomas Jun 15, 2024
cfa49f8
Added Admins
rieglerthomas Jun 15, 2024
79a1415
Enable Azure RBAC
rieglerthomas Jun 15, 2024
a65b1c1
Enable role based access control
rieglerthomas Jun 15, 2024
434b180
Added admin role assignment
rieglerthomas Jun 15, 2024
d630841
Removed variable
rieglerthomas Jun 15, 2024
6135aad
Removed role assignment
rieglerthomas Jun 15, 2024
f9d7dab
Reversed IAC changes of yesterday
rieglerthomas Jun 16, 2024
f066448
Change frontend url
AustrianGam3r Jun 17, 2024
6bc58b3
Add volume mount to controller
AustrianGam3r Jun 17, 2024
d368931
Fix game discovery
AustrianGam3r Jun 18, 2024
4297a7a
Merge branch 'feature/82-filename' into feature/62-cloudretro-ingress
AustrianGam3r Jun 18, 2024
0198372
Add FileName to operator and api crd
AustrianGam3r Jun 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 103 additions & 2 deletions .github/workflows/deploy-az.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ jobs:
- name: Apply tailscale operator
working-directory: ./iac
run: |
az aks command invoke -n ${{ secrets.AZURERM_AKS_CLUSTER_NAME }} -g rg-service-not2day --command "helm repo add tailscale https://pkgs.tailscale.com/helmcharts && helm repo update && helm upgrade --install tailscale-operator tailscale/tailscale-operator --namespace=tailscale --create-namespace --set-string oauth.clientId=${{secrets.TAILSCALE_CLIENT_ID}} --set-string oauth.clientSecret=${{secrets.TAILSCALE_CLIENT_SECRET}} --set-string apiServerProxyConfig.mode=true --wait"

az aks command invoke -n ${{ secrets.AZURERM_AKS_CLUSTER_NAME }} -g rg-service-not2day --command "helm repo add tailscale https://pkgs.tailscale.com/helmcharts && helm repo update && helm upgrade --install tailscale-operator tailscale/tailscale-operator --set-string oauth.clientId=${{secrets.TAILSCALE_CLIENT_ID}} --set-string oauth.clientSecret=${{secrets.TAILSCALE_CLIENT_SECRET}} --set-string apiServerProxyConfig.mode=true --wait || true"
- name: Connect to tailscale
uses: tailscale/github-action@v2
with:
Expand All @@ -62,6 +61,105 @@ jobs:
- name: Check working cluster
run: kubectl get pods -A

- name: Initialize CSI
working-directory: ./scripts/azure-csi
run: |
STORAGE_KEY=$(az storage account keys list --resource-group rg-management-not2day --account-name ${{secrets.AZURERM_GAME_STORAGE_ACCOUNT_NAME}} --query "[0].value" -o tsv)
kubectl create secret generic azure-secret --from-literal=azurestorageaccountname=${{secrets.AZURERM_GAME_STORAGE_ACCOUNT_NAME}} --from-literal=azurestorageaccountkey=$STORAGE_KEY
kubectl apply -f initialize-csi-storage.yaml

- name: Initialize Cluster permissions
working-directory: ./scripts/cluster-permissions
run: |
kubectl apply -f api_cluster_permission.yaml

- name: Install MySQL
working-directory: ./helm/mysql
run: |
helm repo add mysql-operator https://mysql.github.io/mysql-operator/
helm repo update
helm install mysql-operator mysql-operator/mysql-operator --version "2.1.3" --wait \
--create-namespace --namespace=mysql-operator || true
helm install mysql mysql-operator/mysql-innodbcluster --version "2.1.3" --wait \
--create-namespace --namespace=mysql -f values.yaml \
--set-string credentials.root.password=${{ secrets.MYSQL_ROOT_PASSWORD }} || true

- name: Install Open Policy Gatekeeper
working-directory: ./scripts/opa
run: |
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
helm install gatekeeper gatekeeper/gatekeeper --namespace gatekeeper-system --create-namespace --wait || true
kubectl apply -f loadbalancerclass_mutator.yaml

- name: Install stunner
working-directory: ./scripts/localenv
run: make install_stunner || true

- name: Install game operator manifests
working-directory: ./operator
run: make install

- name: Deploy game operator
working-directory: ./operator
run: make deploy IMG=${{ env.REGISTRY }}/${{ env.NAMESPACE }}/${{ env.SUB_NAMESPACE }}/operator:${{ env.LABEL }}

# - name: Wait for MySQL to be ready
# run: |
# while true; do
# POD_STATUS=$(kubectl get pod mysql-0 -n mysql --no-headers -o custom-columns=":status.phase" 2>/dev/null);
# if [ "$POD_STATUS" ]; then
# echo "Pod mysql-0 has been created with status: $POD_STATUS";
# break;
# else
# echo "Waiting for pod mysql-0 to be created...";
# sleep 5;
# fi
# done
# kubectl wait --for=condition=Ready pod/mysql-0 -n mysql --timeout=120s

# while true; do
# POD_STATUS=$(kubectl get pod -l app.kubernetes.io/component=router -n mysql --no-headers -o custom-columns=":status.phase" 2>/dev/null); \
# if [ "$POD_STATUS" ]; then
# echo "MySQL router has been created with status: $POD_STATUS";
# break;
# else
# echo "Waiting for MySQL router to be created...";
# sleep 5;
# fi
# done
# kubectl wait --for=condition=ready pod -l app.kubernetes.io/component=router -n mysql --timeout=120s

# - name: Install API
# working-directory: ./helm/api
# run: |
# helm install -f values.yaml \
# --set-string env.mysqlRootPassword=${{ secrets.MYSQL_ROOT_PASSWORD }} \
# --set-string env.azureTenantId=${{ secrets.AZURE_TENANT_ID }}
# --set-string env.azureClientId=${{ secrets.CLIENT_ID }}
# --set-string env.azureClientSecret=${{ secrets.CLIENT_SECRET }}
# --set-string env.azureStorageAccount=${{ secrets.AZURERM_STORAGE_ACCOUNT_NAME }}
# --set-string env.azureContainerName=${{ secrets.AZURERM_CONTAINER_NAME }}
# --set-string env.azureAksClusterName=${{ secrets.AZURERM_AKS_CLUSTER_NAME }}
# --set-string env.azurermSubscriptionId=${{ secrets.AZURERM_SUBSCRIPTION_ID }}
# --set-string env.azurermResourceGroupName=${{ secrets.AZURERM_RESOURCE_GROUP_NAME }}
# --set-string image.label=${{ env.LABEL }} \
# api .

# - name: Wait for external IP of API
# run: |
# until [ -n "$(kubectl get svc api -n api -o jsonpath='{.status.loadBalancer.ingress[0].ip}')" ]; do
# sleep 5
# done

# - name: Install frontend
# working-directory: ./helm/frontend
# run: |
# helm install -f values.yaml \
# --set-string appConfig.apiUrl=http://$(kubectl get svc api -n api -o jsonpath='{.status.loadBalancer.ingress[0].ip}'):$(kubectl get svc api -n api -o jsonpath='{.spec.ports[0].port}') \
# --set-string image.label=${{ env.LABEL }} \
# frontend .

- name: Logout of Azure
run: az logout

Expand All @@ -84,6 +182,9 @@ jobs:
- name: Login to Azure
run: az login --service-principal -u ${{ secrets.CLIENT_ID }} -p ${{ secrets.CLIENT_SECRET }} --tenant ${{ secrets.AZURERM_TENANT_ID }}




- name: Terraform Apply
working-directory: ./iac
run: |
Expand Down
5 changes: 3 additions & 2 deletions api/apis/k8sApi.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"api/models"
"context"
"errors"

"github.com/google/uuid"
streamv1 "indiegamestream.com/indiegamestream/api/stream/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
Expand Down Expand Up @@ -87,8 +88,8 @@ func createAndVerifyGameResource(game *models.Game) (*streamv1.Game, error) {
Namespace: "default",
},
Spec: streamv1.GameSpec{
Name: game.Title,
ExecutableURL: game.StorageLocation,
Name: game.Title,
FileName: game.FileName,
},
}, nil
}
Expand Down
2 changes: 2 additions & 0 deletions api/migrations/2_game_filename.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ALTER TABLE games ADD FileName varchar(512);
INSERT INTO db_state VALUES (2);
5 changes: 3 additions & 2 deletions api/models/game.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ type Game struct {
StorageLocation string `json:"storageLocation"`
Status shared.GameStatus `json:"status"`
Url string `json:"url"`
Owner string `json:"owner"`
}
Owner string `json:"owner"`
FileName string `json:"fileName"`
}
12 changes: 6 additions & 6 deletions api/repositories/gameRepository.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (g gameRepository) FindAllByOwner(owner string) ([]models.Game, error) {
func (g gameRepository) FindByID(id uuid.UUID) (*models.Game, error) {
var game models.Game
err := g.db.QueryRow("SELECT * FROM games WHERE ID = ?", id).
Scan(&game.ID, &game.Title, &game.StorageLocation, &game.Status, &game.Url, &game.Owner)
Scan(&game.ID, &game.Title, &game.StorageLocation, &game.Status, &game.Url, &game.Owner, &game.FileName)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil
Expand All @@ -74,24 +74,24 @@ func (g gameRepository) Save(game *models.Game) error {

if existing != nil {
//If yes, update the existing entry
stmt, err := g.db.Prepare("UPDATE games SET Title=?, StorageLocation=?, Status=?, Url=? WHERE ID = ?")
stmt, err := g.db.Prepare("UPDATE games SET Title=?, StorageLocation=?, Status=?, Url=?, FileName=? WHERE ID = ?")
if err != nil {
return err
}

return checkResult(stmt.Exec(game.Title, game.StorageLocation, game.Status, game.Url, game.ID))
return checkResult(stmt.Exec(game.Title, game.StorageLocation, game.Status, game.Url, game.ID, game.FileName))
}
} else {
game.ID = uuid.New()
}

//If not create a new one
stmt, err := g.db.Prepare("INSERT INTO games (ID, Title, StorageLocation, Status, Url, Owner) VALUES (?,?,?,?,?,?)")
stmt, err := g.db.Prepare("INSERT INTO games (ID, Title, StorageLocation, Status, Url, Owner, FileName) VALUES (?,?,?,?,?,?,?)")
if err != nil {
return err
}

return checkResult(stmt.Exec(game.ID, game.Title, game.StorageLocation, game.Status, game.Url, game.Owner))
return checkResult(stmt.Exec(game.ID, game.Title, game.StorageLocation, game.Status, game.Url, game.Owner, game.FileName))
}

// Delete removes the entry with a specific id from the games database.
Expand Down Expand Up @@ -128,7 +128,7 @@ func readGamesFromRows(query *sql.Rows) ([]models.Game, error) {
var games = []models.Game{}
for query.Next() {
var game models.Game
err := query.Scan(&game.ID, &game.Title, &game.StorageLocation, &game.Status, &game.Url, &game.Owner)
err := query.Scan(&game.ID, &game.Title, &game.StorageLocation, &game.Status, &game.Url, &game.Owner, &game.FileName)
if err != nil {
return nil, err
}
Expand Down
1 change: 1 addition & 0 deletions api/services/gameService.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func (g gameService) Save(fileHeader *multipart.FileHeader, title string, owner
Status: shared.Status_New,
Url: "",
Owner: owner,
FileName: fileHeader.Filename,
}

//Upload game to azure blob storage container
Expand Down
40 changes: 26 additions & 14 deletions api/tests/gameRepository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,11 @@ func Test_Create_Game_Without_Id_Should_Succeed_And_SetId(t *testing.T) {
Status: "MockStatus",
Url: "MockUrl",
Owner: "MockOwner",
FileName: "TestFile.nes",
}
mock.ExpectPrepare(regexp.QuoteMeta("INSERT INTO games"))
mock.ExpectExec(regexp.QuoteMeta("INSERT INTO games")).
WithArgs(sqlmock.AnyArg(), game.Title, game.StorageLocation, game.Status, game.Url, game.Owner).
WithArgs(sqlmock.AnyArg(), game.Title, game.StorageLocation, game.Status, game.Url, game.Owner, game.FileName).
WillReturnResult(sqlmock.NewResult(0, 1))

//Run the test
Expand Down Expand Up @@ -128,6 +129,7 @@ func Test_Create_Game_With_Id_Should_Succeed(t *testing.T) {
Status: "MockStatus",
Url: "MockUrl",
Owner: "MockOwner",
FileName: "TestFile.nes",
}

mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM games WHERE ID = ?")).
Expand All @@ -136,7 +138,7 @@ func Test_Create_Game_With_Id_Should_Succeed(t *testing.T) {
mock.ExpectPrepare("INSERT INTO games")

mock.ExpectExec("INSERT INTO games").
WithArgs(game.ID, game.Title, game.StorageLocation, game.Status, game.Url, game.Owner).
WithArgs(game.ID, game.Title, game.StorageLocation, game.Status, game.Url, game.Owner, game.FileName).
WillReturnResult(sqlmock.NewResult(0, 1))

//Run the test
Expand Down Expand Up @@ -176,20 +178,21 @@ func Test_Save_Existing_Game_Should_Succeed(t *testing.T) {
Status: "MockStatus",
Url: "MockUrl",
Owner: "MockOwner",
FileName: "TestFile.nes",
}

mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM games WHERE ID = ?")).
WithArgs(id).WillReturnRows(
sqlmock.NewRows([]string{"Id", "Title", "StorageLocation", "Status", "Url", "Owner"}).
AddRow(id, "", "", "", "", ""),
sqlmock.NewRows([]string{"Id", "Title", "StorageLocation", "Status", "Url", "Owner", "FileName"}).
AddRow(id, "", "", "", "", "", ""),
)

mock.ExpectPrepare(regexp.
QuoteMeta("UPDATE games SET Title=?, StorageLocation=?, Status=?, Url=? WHERE ID = ?"))
QuoteMeta("UPDATE games SET Title=?, StorageLocation=?, Status=?, Url=?, FileName=? WHERE ID = ?"))

mock.ExpectExec(regexp.
QuoteMeta("UPDATE games SET Title=?, StorageLocation=?, Status=?, Url=? WHERE ID = ?")).
WithArgs(game.Title, game.StorageLocation, game.Status, game.Url, game.ID).
QuoteMeta("UPDATE games SET Title=?, StorageLocation=?, Status=?, Url=?, FileName=? WHERE ID = ?")).
WithArgs(game.Title, game.StorageLocation, game.Status, game.Url, game.ID, game.FileName).
WillReturnResult(sqlmock.NewResult(0, 1))

//Run the test
Expand Down Expand Up @@ -229,12 +232,13 @@ func Test_Find_Game_By_Id_Should_Succeed(t *testing.T) {
Status: "MockStatus",
Url: "MockUrl",
Owner: "MockOwner",
FileName: "TestFile.nes",
}

mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM games WHERE ID = ?")).
WithArgs(id).WillReturnRows(
sqlmock.NewRows([]string{"Id", "Title", "StorageLocation", "Status", "Url", "Owner"}).
AddRow(id, game.Title, game.StorageLocation, game.Status, game.Url, game.Owner),
sqlmock.NewRows([]string{"Id", "Title", "StorageLocation", "Status", "Url", "Owner", "FileName"}).
AddRow(id, game.Title, game.StorageLocation, game.Status, game.Url, game.Owner, game.FileName),
)

//Run the test
Expand Down Expand Up @@ -295,6 +299,7 @@ func Test_Find_Two_Games_Of_Same_Owner_Should_Succeed(t *testing.T) {
Status: "AMockC",
Url: "AMockD",
Owner: "MockOwner",
FileName: "TestFile.nes",
}

gameB := models.Game{
Expand All @@ -304,15 +309,16 @@ func Test_Find_Two_Games_Of_Same_Owner_Should_Succeed(t *testing.T) {
Status: "BMockC",
Url: "BMockD",
Owner: "MockOwner",
FileName: "TestFile2.nes",
}

mock.ExpectPrepare(regexp.QuoteMeta("SELECT * FROM games WHERE owner = ?"))
mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM games WHERE owner = ?")).
WithArgs("MockOwner").
WillReturnRows(
sqlmock.NewRows([]string{"ID", "Title", "StorageLocation", "Status", "Url", "Owner"}).
AddRow(gameA.ID, gameA.Title, gameA.StorageLocation, gameA.Status, gameA.Url, gameA.Owner).
AddRow(gameB.ID, gameB.Title, gameB.StorageLocation, gameB.Status, gameB.Url, gameB.Owner),
sqlmock.NewRows([]string{"ID", "Title", "StorageLocation", "Status", "Url", "Owner", "FileName"}).
AddRow(gameA.ID, gameA.Title, gameA.StorageLocation, gameA.Status, gameA.Url, gameA.Owner, gameA.FileName).
AddRow(gameB.ID, gameB.Title, gameB.StorageLocation, gameB.Status, gameB.Url, gameB.Owner, gameB.FileName),
)

//Run the test
Expand Down Expand Up @@ -350,7 +356,7 @@ func Test_Find_Games_When_Database_Is_Empty_Should_Succeed(t *testing.T) {
mock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM games WHERE owner = ?")).
WithArgs("MockOwner").
WillReturnRows(
sqlmock.NewRows([]string{"Id", "Title", "StorageLocation", "Status", "Url", "Owner"}),
sqlmock.NewRows([]string{"Id", "Title", "StorageLocation", "Status", "Url", "Owner", "FileName"}),
)

//Run the test
Expand Down Expand Up @@ -389,11 +395,12 @@ func Test_Read_Owner_Should_Succeed(t *testing.T) {
Status: "MockStatus",
Url: "MockUrl",
Owner: "MockOwner",
FileName: "TestFile.nes",
}

mock.ExpectQuery(regexp.QuoteMeta("SELECT Owner FROM games WHERE ID = ?")).
WithArgs(id).
WillReturnRows(sqlmock.NewRows([]string{"Id", "Title", "StorageLocation", "Status", "Url", "Owner"}))
WillReturnRows(sqlmock.NewRows([]string{"Id", "Title", "StorageLocation", "Status", "Url", "Owner", "FileName"}))

//Run the test
repository := repositories.GameRepository(db)
Expand Down Expand Up @@ -427,6 +434,7 @@ func Test_Read_Owner_Should_Throw_When_Database_Is_Empty(t *testing.T) {
Status: "MockStatus",
Url: "MockUrl",
Owner: "MockOwner",
FileName: "TestFile.nes",
}

mock.ExpectQuery(regexp.QuoteMeta("SELECT Owner FROM games WHERE ID = ?")).
Expand Down Expand Up @@ -483,4 +491,8 @@ func compareGames(t *testing.T, game *models.Game, res *models.Game) {
t.Errorf("game owner has been changed")
}

if game.FileName != res.FileName {
t.Errorf("game file name has been changed")
}

}
5 changes: 2 additions & 3 deletions operator/api/stream/v1/game_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,8 @@ import (
// GameSpec defines the desired state of Game
type GameSpec struct {
// Name of the game
Name string `json:"name"`
// ExecutableURL is the URL of the game executable, which will be downloaded and mounted into the container
ExecutableURL string `json:"executableURL"`
Name string `json:"name"`
FileName string `json:"filename"`
}

// GameStatus defines the observed state of Game
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,13 @@ spec:
spec:
description: GameSpec defines the desired state of Game
properties:
executableURL:
description: ExecutableURL is the URL of the game executable, which
will be downloaded and mounted into the container
filename:
type: string
name:
description: Name of the game
type: string
required:
- executableURL
- filename
- name
type: object
status:
Expand Down
2 changes: 2 additions & 0 deletions operator/config/rbac/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ resources:
- role_binding.yaml
- leader_election_role.yaml
- leader_election_role_binding.yaml
- game_editor_role.yaml
- game_viewer_role.yaml
# Comment the following 4 lines if you want to disable
# the auth proxy (https://github.com/brancz/kube-rbac-proxy)
# which protects your /metrics endpoint.
Expand Down
2 changes: 1 addition & 1 deletion operator/config/samples/stream_v1_game.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ metadata:
name: game-sample2
spec:
name: "SPECGAME2"
executableURL: "https://example.com/game"
fileName: "varooom-3d (1).gba"
Loading