Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: github repo listing apis & customer's app deploy request #43

Merged
merged 96 commits into from
Aug 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
3014a89
Revert "Merge remote-tracking branch 'origin/feature/#17-blueprint' i…
nookcoder Jul 17, 2023
a4281ef
Merge pull request #18 from sw-maestro-kumofactory/feature/#17-blueprint
nookcoder Jul 17, 2023
e8ccf14
Merge pull request #21 from sw-maestro-kumofactory/main
nookcoder Jul 17, 2023
582fda6
feat : add refresh token from cookie
nookcoder Jul 17, 2023
a646072
fix : access token, refresh token 유효 시간 수정
nookcoder Jul 17, 2023
18b11d6
refactor : jwt folder from cloud/ to auth/
nookcoder Jul 17, 2023
1ec4677
feat : refresh token 만료시 cookie 제거 코드 추가
nookcoder Jul 17, 2023
ff82225
feat : add feature logout and withdraw
nookcoder Jul 20, 2023
4b1d697
update : resolve conflict code
nookcoder Jul 20, 2023
c32b094
Merge pull request #22 from sw-maestro-kumofactory/feature/#20-auth
nookcoder Jul 20, 2023
4283ceb
fix : import dependency
nookcoder Jul 20, 2023
1bd5c25
fix : 으앙
nookcoder Jul 20, 2023
ebbcef4
fix : import RequestContextHolder in AuthorizationFromTokenAspect
nookcoder Jul 20, 2023
6d22ab0
update : -name to -t
nookcoder Jul 20, 2023
d14b2c5
delete : delete docker stop
nookcoder Jul 20, 2023
f3a774c
feat: api for listing repository and organization #16
coding-convention Jul 31, 2023
e0b415c
refactor: discard some feature #16
coding-convention Jul 31, 2023
c28f9ad
feat: verify Dockerfile existence #16
coding-convention Jul 31, 2023
cfd83c6
feat: apis for build pages #16
coding-convention Jul 31, 2023
30dcb64
fix: Json naming convention #16
coding-convention Aug 1, 2023
1b34f18
feat : add validating Enum type value
nookcoder Aug 4, 2023
9ea3ae5
feat : add column options, az, scope in AwsComponent
nookcoder Aug 4, 2023
e7c6938
feat: add staging.yml
nookcoder Aug 5, 2023
036de9f
feat: add AwsCdkDto
nookcoder Aug 5, 2023
ac3eeae
update : update staging.yml
nookcoder Aug 5, 2023
e4a5dbd
refactor : refactor
nookcoder Aug 5, 2023
9ae2320
Merge pull request #26 from sw-maestro-kumofactory/staging
nookcoder Aug 5, 2023
82f71db
update: update login to ecr in staging.yml
nookcoder Aug 5, 2023
e2e5fc2
Merge pull request #27 from sw-maestro-kumofactory/feature/#25-deploy…
nookcoder Aug 5, 2023
a8d47f3
update : update ecr repository name
nookcoder Aug 5, 2023
a05b9e2
Merge pull request #28 from sw-maestro-kumofactory/feature/#25-deploy…
nookcoder Aug 5, 2023
2d66f0f
fix : / 1개 삭제
nookcoder Aug 5, 2023
5d31c1e
Merge pull request #29 from sw-maestro-kumofactory/feature/#25-deploy…
nookcoder Aug 5, 2023
d7c0a00
test : check yml file content
nookcoder Aug 5, 2023
e0347da
Merge pull request #30 from sw-maestro-kumofactory/feature/#25-deploy…
nookcoder Aug 5, 2023
75c0450
update : update rabbitmq host url
nookcoder Aug 5, 2023
9d9b27e
Merge pull request #31 from sw-maestro-kumofactory/feature/#25-deploy…
nookcoder Aug 5, 2023
3809d56
update : add uuid in blueprint dto
nookcoder Aug 6, 2023
f4200a1
update : add az in AwsCdkDto
nookcoder Aug 6, 2023
54a1461
update : update awscomponentype
nookcoder Aug 7, 2023
61d6e0f
Merge pull request #32 from sw-maestro-kumofactory/feature/#25-deploy…
nookcoder Aug 7, 2023
256b5d2
fix: typo #16
coding-convention Aug 7, 2023
3af2850
af
nookcoder Aug 7, 2023
d654701
feat : add enum type ProvisionStatus
nookcoder Aug 7, 2023
3610ffc
feat : select blueprint by uuid, store provision status
nookcoder Aug 7, 2023
6df2f58
update : update AwsComponentType
nookcoder Aug 7, 2023
2cab0d7
Merge branch 'feature/#25-deploy-aws-cdk' into staging
nookcoder Aug 7, 2023
40eed4f
chore : add swagger-ui
nookcoder Aug 7, 2023
86978d8
Merge branch 'staging' into feature/#25-deploy-aws-cdk
nookcoder Aug 7, 2023
8defe75
feat : select blueprint by uuid and add status
nookcoder Aug 7, 2023
4fd6af6
Merge branch 'feature/#25-deploy-aws-cdk' into staging
nookcoder Aug 7, 2023
ff9a717
Merge branch 'staging' into feature/#25-deploy-aws-cdk
nookcoder Aug 7, 2023
6fbc320
ff
nookcoder Aug 7, 2023
8bb8755
Merge pull request #34 from sw-maestro-kumofactory/staging
nookcoder Aug 7, 2023
823120c
add : swagger 에 get dto 명세 추가
nookcoder Aug 7, 2023
ded34b4
Merge remote-tracking branch 'origin/feature/#25-deploy-aws-cdk' into…
nookcoder Aug 7, 2023
f72ef97
Merge branch 'staging' into feature/#25-deploy-aws-cdk
nookcoder Aug 7, 2023
00f1ba9
feat : area 속성 추가
nookcoder Aug 7, 2023
111be7c
Merge pull request #35 from sw-maestro-kumofactory/feature/#25-deploy…
nookcoder Aug 7, 2023
2301b9e
update : add AZ in AwsAreaType
nookcoder Aug 7, 2023
fbe4ef1
Merge pull request #36 from sw-maestro-kumofactory/feature/#25-deploy…
nookcoder Aug 7, 2023
476b435
feat: Util for AWS S3 #33
coding-convention Aug 7, 2023
5088e03
feat: S3 config #33
coding-convention Aug 7, 2023
ee077d4
feat: add thumbnail content to dtos #33
coding-convention Aug 7, 2023
ca55228
feat: add thumbnail content to impl #33
coding-convention Aug 7, 2023
5a9ddf4
Merge branch 'staging' into feature/#33-thumbnail
nookcoder Aug 8, 2023
4d7a5cc
Merge pull request #37 from sw-maestro-kumofactory/feature/#33-thumbnail
nookcoder Aug 8, 2023
69e1579
Merge branch 'staging' into feature/#25-deploy-aws-cdk
nookcoder Aug 8, 2023
e6f9742
fix: fix error
nookcoder Aug 8, 2023
5e0f6ca
Merge branch 'staging' into feature/#25-deploy-aws-cdk
nookcoder Aug 8, 2023
b2e0606
update : RDS -> RDS_MYSQL
nookcoder Aug 8, 2023
148d7b1
Merge branch 'feature/#25-deploy-aws-cdk' into staging
nookcoder Aug 8, 2023
93ca349
feat : delete blueprint
nookcoder Aug 8, 2023
adc349c
Merge pull request #38 from sw-maestro-kumofactory/feature/#25-deploy…
nookcoder Aug 8, 2023
f4acb35
fix: method call #33
coding-convention Aug 8, 2023
d3ce6e2
feat : blueprint scope 상태 추가 및 api 추가
nookcoder Aug 8, 2023
43fe8e4
feat : set env from application.yml
nookcoder Aug 8, 2023
ab842a2
fix: Convert frontend to send Blob instead of binary
coding-convention Aug 8, 2023
aa4b6d2
feat : add s3 keyname
nookcoder Aug 9, 2023
5f6da6e
feat : add templatePreviewDto
nookcoder Aug 9, 2023
bd77885
#25-deploy-aws-cdk: Auto stash before merge of "feature/#25-deploy-aw…
nookcoder Aug 9, 2023
bcfb11a
Merge branch 'staging' into feature/#25-deploy-aws-cdk
nookcoder Aug 9, 2023
0a60364
Merge pull request #39 from sw-maestro-kumofactory/feature/#25-deploy…
nookcoder Aug 9, 2023
b47a114
Merge branch 'staging' into feature/#25-deploy-aws-cdk
nookcoder Aug 9, 2023
8d9ca9f
fix : rename variable name
nookcoder Aug 9, 2023
93b7506
Merge pull request #40 from sw-maestro-kumofactory/feature/#25-deploy…
nookcoder Aug 9, 2023
cfc3106
fix ; return null in searchTemplateFromTemplateName
nookcoder Aug 9, 2023
176ec48
Merge pull request #41 from sw-maestro-kumofactory/feature/#25-deploy…
nookcoder Aug 9, 2023
b6ddcb6
feat: get credentials provider via yaml instead .aws/credential
coding-convention Aug 9, 2023
b33e08a
Merge remote-tracking branch 'origin/staging' into staging
coding-convention Aug 9, 2023
64b273d
Merge remote-tracking branch 'origin/staging' into feature/#16-build-…
coding-convention Aug 9, 2023
27f3e92
feat: add accessToken, profileName columns #16
coding-convention Aug 9, 2023
862a1ae
feat: find member via oauthId #16
coding-convention Aug 9, 2023
116a187
feat: store profileName and GitHubAccessToke #16
coding-convention Aug 9, 2023
b0d5c17
chore: trivial thing #16
coding-convention Aug 9, 2023
06e9151
chore: find member via oauthId #16
coding-convention Aug 9, 2023
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
13 changes: 6 additions & 7 deletions .github/workflows/Build-Deploy-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
with:
java-version: 17
distribution: 'oracle'

- name: Setup Gradle 7.6
uses: gradle/gradle-build-action@v2
with:
Expand All @@ -45,15 +45,15 @@ jobs:
shell: bash

- name: Build Project
run: gradle build
run: ./gradlew clean build

############################################
### Start Pushing container image on ECR ###
############################################
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}

Expand All @@ -76,7 +76,7 @@ jobs:

docker push $ECR_REGISTRY/$ECR_REPOSITORY:dev-$DATE
docker push $ECR_REGISTRY/$ECR_REPOSITORY:dev-latest

#########################################################
# MAIN # MAIN # MAIN # MAIN # MAIN # MAIN # MAIN # MAIN #
#########################################################
Expand All @@ -92,7 +92,7 @@ jobs:

docker push $ECR_REGISTRY/$ECR_REPOSITORY:main-$DATE
docker push $ECR_REGISTRY/$ECR_REPOSITORY:main-latest


deploy:
needs: build
Expand All @@ -114,7 +114,6 @@ jobs:
password=$(aws ecr get-login-password --region ${{ env.AWS_REGION }})
docker login --username AWS --password $password ${{ env.ECR_REGISTRY }}
docker pull ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:dev-latest
docker stop ${{ env.ECR_REPOSITORY }}-dev
docker run --rm -d -p 8081:8080 --name kumo-server-dev ${{ env.ECR_REGISTRY }}/${{ env.ECR_REPOSITORY }}:dev-latest

#########################################################
Expand Down
62 changes: 62 additions & 0 deletions .github/workflows/Staging.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Build and Push to ECR
on:
push:
branches:
- staging # Change this to your desired branch
env:
REGION: ap-northeast-2 # Seoul


jobs:
build-and-push:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- uses: actions/checkout@v3
- uses: actions/setup-java@v3
name: Set up JDK 17
with:
distribution: 'temurin'
java-version: '17'
check-latest: true

- name: make application.yml
run: |
mkdir ./src/main/resources
cd ./src/main/resources
touch ./application.yml
echo "${{ secrets.YML }}" > ./application.yml
shell: bash

- name: make application-dev.yml
run: |
cd ./src/main/resources
touch ./application-dev.yml
echo "${{ secrets.YML_STAGING }}" > ./application-dev.yml
cat ./application-dev.yml
shell: bash

- name: Build Spring Boot application
run: ./gradlew clean build

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.REGION }}

- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1

- name: Build and tag Docker image
run: |
docker build -t ${{ secrets.ECR_REGISTRY }}/dev:latest .

- name: Push Docker image to ECR
run: |
docker push ${{ secrets.ECR_REGISTRY }}/dev:latest
9 changes: 9 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ repositories {
}

dependencies {
implementation 'org.springframework:spring-test:5.3.10'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.amqp:spring-rabbit-test'
implementation 'mysql:mysql-connector-java:8.0.33'
implementation 'org.springframework.boot:spring-boot-starter-validation'
compileOnly 'org.projectlombok:lombok:1.18.24'
Expand All @@ -33,6 +35,13 @@ dependencies {
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'

implementation "io.hypersistence:hypersistence-utils-hibernate-55:3.5.0"
implementation 'org.springdoc:springdoc-openapi-ui:1.6.9'
implementation 'software.amazon.awssdk:s3:2.17.112'

// dependency of rabbitmq
implementation 'org.springframework.boot:spring-boot-starter-amqp'
}

tasks.named('test') {
Expand Down
21 changes: 17 additions & 4 deletions src/main/java/com/kumofactory/cloud/AppController.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
package com.kumofactory.cloud;

import com.kumofactory.cloud.auth.jwt.dto.TokenDto;
import com.kumofactory.cloud.auth.jwt.provider.JwtTokenProvider;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class AppController {
@GetMapping("/")
public String checkHealth() {
return "success";
}
private final JwtTokenProvider provider;

@GetMapping("/")
public String checkHealth() {
return "success";
}

@GetMapping("/token")
public String createMockTest() {
TokenDto tokenDto = provider.create("11");
return tokenDto.getAccessToken();
}
}
12 changes: 9 additions & 3 deletions src/main/java/com/kumofactory/cloud/CloudApplication.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
package com.kumofactory.cloud;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

import java.nio.charset.StandardCharsets;

@SpringBootApplication
public class CloudApplication {
private final static String QUEUE_NAME = "hello";

public static void main(String[] args) {
SpringApplication.run(CloudApplication.class, args);
}
public static void main(String[] args) {
SpringApplication.run(CloudApplication.class, args);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.kumofactory.cloud.appDeploy;

import com.kumofactory.cloud.appDeploy.dto.BuildRequestDto;
import com.kumofactory.cloud.appDeploy.dto.GitHubRepoDto;
import com.kumofactory.cloud.appDeploy.service.BuildRequestService;
import com.kumofactory.cloud.appDeploy.service.UserRepoService;
import com.kumofactory.cloud.global.annotation.auth.AuthorizationFromToken;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/build")
@RequiredArgsConstructor
@Slf4j
public class AppDeployController {
private final Logger logger = org.slf4j.LoggerFactory.getLogger(AppDeployController.class);

private final UserRepoService userRepoService;
private final BuildRequestService buildRequestService;

@GetMapping("/list/{org}/repo")
@AuthorizationFromToken
public List<GitHubRepoDto.RepoInfoDto> listOrgRepo(@PathVariable String org, String userId) {
return userRepoService.RequestOrgRepoInfo(org, userId);
}

@GetMapping("/list")
@AuthorizationFromToken
public GitHubRepoDto.UserDto listUserRepoAndOrgs(String userId) {
return userRepoService.RequestUserRepoInfoAndOrgList(userId);
}

@GetMapping("/list/{org}/{repo}/branch")
@AuthorizationFromToken
public List<String> listRepoBranches(@PathVariable String org, @PathVariable String repo, String userId) {
return userRepoService.RequestRepoBranches(org, repo, userId);
}

@GetMapping("/list/{repo}/branch")
@AuthorizationFromToken
public List<String> listRepoBranches(@PathVariable String repo, String userId) {
return userRepoService.RequestRepoBranches(repo, userId);
}

@PostMapping("/deploy")
@AuthorizationFromToken
public ResponseEntity<String> deployRequest(@RequestBody BuildRequestDto request, String userId) {
buildRequestService.RequestBuild(request, userId);
return ResponseEntity.ok("success");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.kumofactory.cloud.appDeploy.dto;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.List;

public record BuildRequestDto(
@JsonProperty("targetInstance") String instanceId, // required
@JsonProperty("githubToken") String gitHubToken,
@JsonProperty("user") String user, // required
@JsonProperty("repo") String repo, // required
@JsonProperty("branch") String branch, // required
@JsonProperty("Dockerfile") Boolean Dockerfile,
@JsonProperty("language") String language, // required
@JsonProperty("env") List<EnvInfoDTO> env) // required
{
public record EnvInfoDTO(
@JsonProperty("key") String key,
@JsonProperty("value") String value
) { }

public BuildRequestDto setDockerfile(Boolean Dockerfile) {
return new BuildRequestDto(instanceId, gitHubToken, user, repo, branch, Dockerfile, language, env);
}

public BuildRequestDto setgithubToken(String gitHubToken) {
return new BuildRequestDto(instanceId, gitHubToken, user, repo, branch, Dockerfile, language, env);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.kumofactory.cloud.appDeploy.dto;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.List;

public class GitHubRepoDto {
public record RepoInfoDto(
@JsonProperty("name") String name,
@JsonProperty("fullName") String fullName,
@JsonProperty("private") Boolean isPrivate,
@JsonProperty("fork") Boolean isFork
) { }

public record UserDto(
// @JsonProperty("personal_repo_count") Integer personalRepoCount,
// @JsonProperty("personal_repo") List<RepoInfoDto> personalRepo,
// @JsonProperty("organization_count") Integer organizationCount,
// @JsonProperty("organization") List<String> organizationList
@JsonProperty("repoCount") Integer personalRepoCount,
@JsonProperty("repoInfo") List<RepoInfoDto> personalRepo,
@JsonProperty("orgCount") Integer organizationCount,
@JsonProperty("orgList") List<String> organizationList
) { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.kumofactory.cloud.appDeploy.service;

import com.kumofactory.cloud.appDeploy.dto.BuildRequestDto;

public interface BuildRequestService {
public void RequestBuild(BuildRequestDto request, String oauthId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.kumofactory.cloud.appDeploy.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.kumofactory.cloud.appDeploy.dto.BuildRequestDto;
import com.kumofactory.cloud.member.MemberRepository;
import com.kumofactory.cloud.member.domain.Member;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

@Service
@Slf4j
@Repository
@RequiredArgsConstructor
public class BuildRequestServiceImpl implements BuildRequestService {

private final MemberRepository memberRepository;
private final Logger logger = LoggerFactory.getLogger(BuildRequestServiceImpl.class);
private String token;
private String baseUri = "https://api.github.com";
@Value("${build.server}")
private String buildServerUri;

@Override
public void RequestBuild(BuildRequestDto request, String oauthId) {
Member member = memberRepository.findMemberByOauthId(oauthId);
this.token = member.getGithubAccessToken();
String url = buildServerUri + "/api/v1/deploy";
request.setDockerfile(isDockerfileExist(request.user(), request.repo()));
request.setgithubToken(token);

logger.info(request.toString());

HttpHeaders headers = new HttpHeaders();
headers.set("Accept", "application/json");
HttpEntity<BuildRequestDto> httpEntity = new HttpEntity<>(request, headers);
ResponseEntity<String> response = new RestTemplate().exchange(url, HttpMethod.POST, httpEntity, String.class);

if (response.getStatusCode().is2xxSuccessful()) {
logger.info("response : {}", response.getBody());
} else {
logger.error("response : {}", response.getBody());
logger.error("response : {}", response.getStatusCode());
}
}

private Boolean isDockerfileExist(String userName ,String repoName) {
String url = baseUri + "/repos/" + userName + "/" + repoName + "/contents/Dockerfile";
try {
ResponseEntity<JsonNode> response = RequestGitHubAPIs(url);
return true;
} catch (Exception e) {
return false;
}
}

private ResponseEntity<JsonNode> RequestGitHubAPIs(String uri) {
HttpHeaders headers = new HttpHeaders();
headers.set("Accept", "application/json");
if( StringUtils.hasText(token) ) {
headers.setBearerAuth(token);
}
HttpEntity<String> httpEntity = new HttpEntity<>(headers);
return new RestTemplate().exchange(uri, HttpMethod.GET, httpEntity, JsonNode.class);
}
}
Loading
Loading